ProConcepts Utility Network - Esri/arcgis-pro-sdk GitHub Wiki

The utility network is a comprehensive framework of functionality in ArcGIS for modeling utility systems such as electric, gas, water, storm water, wastewater, and telecommunications. This topic provides an introduction to the utility network API. It details the classes and methods that query and edit the utility network. The utility network API is commonly used in conjunction with the geodatabase and editing.

Language:      C#
Subject:       Utility Network
Contributor:   ArcGIS Pro SDK Team <[email protected]>
Organization:  Esri, http://www.esri.com
Date:         05/19/2022
ArcGIS Pro:    3.4
Visual Studio: 2022  

In this topic

Introduction

Overview

  • The utility network API is a Data Manipulation Language (DML)-only API. This means that all schema creation and modification operations such as creating domain networks, adding and deleting rules, and so on, need to use the geoprocessing API.

  • Almost all of the methods in the utility network API should be called on the Main CIM Thread (MCT). The API reference documentation on the methods that need to run on the MCT are specified as such. These method calls should be wrapped inside the QueuedTask.Run call. Failure to do so will result in ConstructedOnWrongThreadException being thrown. See Working with multithreading in ArcGIS Pro to learn more.

  • In standard usage, access to a utility network takes place via services or with file or mobile geodatabases. The utility network API is designed accordingly. Implications of this architecture are described throughout this topic as appropriate.

Note: This topic assumes a basic understanding of the utility network information model. See the online help for more information.

Resource Management

Like the geodatabase API, many objects in the managed utility network API use unmanaged resources (i.e. resources not managed by garbage collection) that must be explicitly released by the application. Unmanaged resources include file locks and database connections, among others. Since the utility network is often a services-based system, many of these resources actually exist on the server.

The preferred pattern to manage these underlying unmanaged resources is by calling Dispose once you are done using them. Dispose frees up the unmanaged resources, releasing any underlying file locks or active database connections (You can also use a “using” construct that will call Dispose for you - there are many examples of “using” in the Pro snippets and samples).

More information on these patterns, along with special cases that you should consider, can be found in the resource management section of the geodatabase ProConcepts doc.

Namespaces and Extension Methods

The main items of the utility network are included in the ArcGIS.Core.Data.UtilityNetwork namespace. Tracing items are included in ArcGIS.Core.Data.UtilityNetwork.Trace.

There are some cases where the objects in ArcGIS.Core.Data need to integrate with ArcGIS Pro at a higher level in the architectural stack. In these cases, C# extension methods are provided. To use these extension methods:

  • Add a reference to ArcGIS.Desktop.Extensions to your solution
  • Add using ArcGIS.Core.Data.UtilityNetwork.Extensions; to the top of your source files

These steps allow these extension methods to appear as if they were regular methods on utility network classes.

These extension methods are intended for use in ArcGIS Pro Add-ins, and cannot be used with CoreHost applications.

Synchronous and Asynchronous Services

Most of the utility network-related REST services are synchronous. These use the Utility Network Server and have low processing overhead. Unfortunately, they can be susceptible to timeout if processing takes too long. To work around this, several of the REST endpoints provide asynchronous versions. Processing is offloaded from the Utility Network Server to the Geoprocessing Server. While this does cause a performance hit, the Geoprocessing Server is designed to handle long-running jobs and is not susceptible to timeouts. With these services, ArcGIS Pro will poll the service until the operation completes.

Some of the C# methods in the utility network API can provide access to the asynchronous versions of these REST endpoints. These take an ServiceSynchronizationType as a parameter, which is an enum with two values: Synchronous and Asynchronous. It's important to note that from a C# developer's perspective, these are synchronous C# methods. Like other methods in the API, they will block the current thread until the operation completes.

When calling these routines on a file or mobile geodatabase, the ServiceSynchronizationType parameter is ignored.

Organization of the utility network API

This topic logically divides the API into nine sections. The following diagram provides a functional organization of the API. The API itself is a collection of classes and is not technically a layered architecture.

  • The UtilityNetwork class is the root object that provides access to the utility network API.
  • The Definition and schema section describes the classes and methods that provide information about the utility network schema.
  • The Element class covers the basic encapsulation of a row in the utility network API.
  • Network topology covers routines that query the topological index.
  • Associations covers routines that query and edit associations between utility network rows.
  • Subnetworks provides classes and routines to query and edit utility network subnetworks.
  • Tracing provides tracing functionality.
  • Network diagrams allow you to query and edit network diagrams.
  • Finally, Pro integration describes how the utility network API integrates with other parts of the Pro SDK.

An object model diagram of the utility network API is provided on the website.

UtilityNetwork Class

Utility networks are implemented in the geodatabase as controller datasets. Other controller datasets in ArcGIS include network datasets for transportation networks, and topology for managing coincident features.

The UtilityNetwork class provides an abstraction of this controller dataset. Methods on this class provide an entry point to the other areas of the utility network API.

As with other datasets in the geodatabase, a UtilityNetwork object can be obtained by calling Geodatabase.OpenDataset(). UtilityNetwork objects can also be obtained from a table or feature class that belongs to a utility network by using Table.GetControllerDatasets(). Note that a particular feature class can belong to multiple controller datasets.

The Geodatabase.OpenDataset() routine takes the name of a dataset to open. Remember that when using feature services, the name of the dataset in the feature service workspace does not match the name in the client-server workspace. You typically need to get the correct name from the corresponding definition object.

A UtilityNetwork object can be obtained from a table as follows:

public static UtilityNetwork GetUtilityNetworkFromTable(Table table)
{
  UtilityNetwork utilityNetwork = null;

  if (table.IsControllerDatasetSupported())
  {
    // Tables can belong to multiple controller datasets, but at most one of them will be a UtilityNetwork

    IReadOnlyList<Dataset> controllerDatasets = table.GetControllerDatasets();
    foreach (Dataset controllerDataset in controllerDatasets)
    {
      if (controllerDataset is UtilityNetwork)
      {
        utilityNetwork = controllerDataset as UtilityNetwork;
      }
      else
      {
        controllerDataset.Dispose();
      }
    }
  }

  return utilityNetwork;
}

In addition to user-editable feature classes, utility networks also contain several other tables that are available to users. Some examples of these are the dirty areas table, the errors table, and the subnetworks table. The IsSystemTableSupported() method on the UtilityNetwork class can be used to determine if the desired system table exists (these tables can vary depending on the version of the utility network schema). These tables are then available by using the GetSystemTable() method.

Definition and schema

The UtilityNetworkDefinition class provides metadata information about the utility network. This class is the entryway to a collection of additional classes that provide metadata about elements in the utility network, such as domain networks and rules. The classes described in this section are all lightweight, value-based objects that are derived from information cached with the feature service.

For more information on the concepts implemented by these objects, see the Utility Network Concepts section of the online help.

  • The utility network is described by the UtilityNetworkDefinition class. This class also serves as the central hub for the definition and schema classes.
  • Every utility network consists of one or more domain networks.
  • Domain networks contain tiers.
  • Every utility network also contains a set of NetworkSource classes. These classes describe the sources of data for the utility network (usually feature classes).
  • Every table that serves as a network source contains asset groups, which contain asset types.
  • Utility networks also contain a set of network attributes.
  • Terminal configurations of a utility network are described by the TerminalConfiguration and Terminal classes.
  • Finally, the rules of a utility network are described by the Rule and RuleElement classes.

UtilityNetworkDefinition class

The UtilityNetworkDefinition class follows the same access pattern used by other definition classes in the geodatabase API.

As with other definition classes, there are two options for accessing UtilityNetworkDefinition:

  • Open the UtilityNetworkDefinition from a geodatabase—Typically this is used when it is not anticipated that the utility network will be opened.
UtilityNetworkDefinition definition = geodatabase.GetDefinition<UtilityNetworkDefinition>("UtilityNetworkName");
  • Open the UtilityNetworkDefinition directly from the UtilityNetwork—This is used when the dataset is already open and the reference is accessible.
UtilityNetworkDefinition definition = utilityNetwork.GetDefinition();

Most of the methods on this class are described in the sections that follow. Additional methods are detailed below:

  • GetAssetGroupField() returns the field name that stores the asset group of a row.
  • GetAssetTypeField() returns the field name that stores the asset type of a row.
  • GetAssociationStatusField() returns the field name of the association status field.
  • GetAvailableCategories() returns a list of the categories that have been set on the utility network using the Add Category geoprocessing tool. Categories are strings that serve as tags to identify asset types in a data model-independent fashion.
  • GetSchemaVersion() returns a string representation of the version of the utility network schema.
  • GetServiceTerritoryEnvelope() returns the extent of the utility network (that is, the extent of the service territory polygon, plus 10 percent).

Domain networks

A domain network represents a type of utility service that an organization serves. Domain networks can represent different levels of the same utility resource such as distribution and transmission levels for gas, water, or electricity. Domain networks can also represent multiple types of commodities such as natural gas and electricity. In addition, every utility network contains a domain network for the structure network.

Calling UtilityNetworkDefinition.GetDomainNetworks returns a read-only list of DomainNetwork objects. Calling UtilityNetworkDefinition.GetDomainNetwork returns the DomainNetwork object with a specific name.

Domain networks can return lists of network sources (DomainNetwork.NetworkSources) and tiers (DomainNetwork.Tiers). Additional properties on the DomainNetwork class are as follows:

Property Description
IsStructureNetwork Returns whether this domain network represents the structure network.
SubnetworkControllerCategory Returns the type of subnetwork controllers supported in this domain network. Valid values in the SubnetworkControllerCategory enum are Source, Sink, and None (for structure networks).
TierDefinition Returns the type of subnetworks supported in this domain network. Valid values in the TierDefinition enum are Hierarchical (typically used with pressure networks) and Partitioned (typically used with electrical networks).

Tiers and Tier Groups

Tiers model the hierarchy of how the network delivers a resource such as natural gas, electricity, or water. A tier typically represents a pressure or voltage level. For example, an electric distribution system can be subdivided into subtransmission, medium-voltage, and low-voltage levels and some types of analysis should be performed only within one of these levels. A tier can also represent parts of the network that can be isolated from one another such as a valve isolation zone in a pressure-based system. Tiers are useful because they allow you to constrain the valid row types for each tier, and they can also define the extent of network tracing analysis.

Tier objects can be obtained with the DomainNetwork.Tiers property, which returns a list of all of the tiers on the DomainNework, or with the DomainNetwork.GetTier method, which returns a single tier by name.

The following code demonstrates how to find a "Medium Voltage" tier from an "Electric Distribution" domain network:

using (UtilityNetworkDefinition utilityNetworkDefinition = utilityNetwork.GetDefinition())
{
  DomainNetwork domainNetwork = utilityNetworkDefinition.GetDomainNetwork(domainNetworkName);
  Tier tier = domainNetwork.GetTier(tierName);
}

When tiers are created using the Add Tier geoprocessing tool, several collections of asset types must be provided. These asset types can be retrieved by the following sets of properties:

Property Description
ValidDevices The device asset types that can be included within subnetworks in this tier
ValidEdgeObjects The edge object asset types that can be included within subnetworks in this tier
ValidJunctionObjects The junction object asset types that can be included within subnetworks in this tier
ValidJunctionObjectSubnetworkControllers The junction object asset types that can serve as subnetwork controllers within this tier
ValidJunctions The junction asset types that can be included within subnetworks in this tier
ValidLines The line asset types that can be included within subnetworks in this tier
ValidSubnetworkControllers The asset types that can serve as subnetwork controllers within this tier
ValidSubnetworkLines The asset types that are used to build features in the SubnetLine feature class when updating the subnetwork

Other subnetwork properties and methods on the Tier class are as follows:

Property or Method Description
DomainNetwork Returns the DomainNetwork that contains this tier.
GetDiagramTemplateNames() Returns the names of the subnetwork diagram templates for this tier.
GetEditModeForSubnetwork Whether events are fired when updating subnetworks for this tier for a particular version type. Events control whether attribute rules fire.
HasUpdateSubnetworkPolicy Whether containers and structures are updated when updating subnetworks.
IsDisjointSubnetworkSupported A disjoint subnetwork contains at least one subnetwork controller that cannot be traversed from its other subnetwork controllers. If this property is false, and the subnetwork is disjointed, calling Subnetwork.Update() will result in an error.
SubnetworkFieldName The name of the field that stores the subnetwork name.
TopologyType The type of subnetworks that are supported. Valid values are Radial, and Mesh.
TraceConfiguration The trace configuration that was set by the Set Subnetwork Definition geoprocessing tool.

Tier Groups

Tier groups provide an extra level of organization for tiers inside hierarchical networks. For example, a gas network may be divided into two tier groups, Transmission and Distribution. Each of these tier groups would contain a set of tiers specific to that group. For example, Distribution Pressure and Distribution Isolation might be tiers inside the Distribution tier group.

Tier groups can be obtained with the DomainNetwork.TierGroups property, which return a list of all of the tier groups on the DomainNetwork.

Network sources

Utility networks are created from different sources of information. The most obvious are the structure feature classes (StructureLine, StructurePoint, and StructureBoundary) and the feature classes that are included with each domain network (Device, Line, Junction, Assembly, and SubnetLine). Other sources of information are the set of associations that have been created, and the system junctions that are automatically generated where needed. These sources of information are collectively known as network sources and are represented by the NetworkSource class.

Network source objects can be obtained with the GetNetworkSource() and GetNetworkSources() methods on the UtilityNetworkDefinition class, as well as from the NetworkSources property on the DomainNetwork class. GetNetworkSource() is used to return a single NetworkSource by name, while GetNetworkSources() returns a list of all the network sources on the UtilityNetworkDefinition or DomainNetwork.

The UsageType property describes how the table is used in the network. Possible values are Device, Junction, Line, Assembly, SubnetLine, StructureJunction, StructureLine, StructureBoundary, SystemJunction, Association, JunctionObject, EdgeObject, StructureJunctionObject, and StructureEdgeObject.

Finally, the GetAssetGroup and GetAssetGroups methods return the asset groups for this particular network source. GetAssetGroup returns a single asset group by name, while GetAssetGroups returns a list of asset groups.

Asset groups and asset types

There are two predefined fields on each table in a domain network and structure network that provide a two-level classification system in each table in a utility network. These type attributes allow you to define row types with specificity while limiting the number of tables, which is important for high performance of the utility network. Most configuration in the utility network is set at the asset type level.

Asset group

Asset groups represent the primary classification of rows in utility network tables. The ASSETGROUP field is the subtype field of all tables in the structure network and domain networks (with the exception of the SubnetLine class).

The following are examples of asset groups:

  • Asset groups for devices in an electric domain network could be Breaker, Capacitor, Fuse, Recloser, Switch, and Transformer.
  • Asset groups for lines in a gas domain network could be Connector, Distribution, GatheringWater, StationPipe, and Transmission.
  • Asset groups for assemblies in a water distribution domain network could be CompressorStation, PumpStation, RegulatorStation, and TownBorderStation.

AssetGroup objects are obtained using the GetAssetGroup or GetAssetGroups method on the NetworkSource class. In turn, they implement GetAssetType and GetAssetTypes to return their constituent asset types. GetAssetType returns a single asset type by name, while GetAssetTypes returns a set of all the asset types for this particular asset group.

Asset type

The AssetType class represents the secondary classification of asset group types. The ASSETTYPE field is implemented as a set of coded-value domains for each asset group.

The following are examples of asset types:

  • Asset types for a transformer asset group in an electric distribution domain network could be StepTransformer, PowerTransformer, and DistributionTransformer.
  • Asset types for a line feature class in a water distribution domain network could be PVCPipe, ClayPipe, and CastIronPipe.

The AssetType class supports a number of properties and methods that describe the characteristics of this type.

Property or Method Description
IsLinearConnectivityPolicySupported() Returns whether or not the GetLinearConnectivityPolicy() method is applicable.
GetLinearConnectivityPolicy() Returns whether connectivity for this asset type can be established at any vertex (AnyVertex) or only at end points (EndVertex).
IsContainerSplitPolicySupported() Returns whether or not the GetContainerSplitPolicy() method is applicable.
GetContainerSplitPolicy() Returns the behavior that occurs when containers with this asset type are split. If set to SplitContent, this means that content features are also split; DoNotSplitContent indicates that content features are not split.
AssociationDeletionSemantics Returns the deletion type for this asset type (Cascade, None, or Restricted).
ContainerViewScale Returns the default scale of any containers created from this asset type. If the asset type is not a container, this routine returns 0.0.
AssociationRoleType Returns whether the asset type can be a Container, Structure, or None (neither).
GetCategoryList() Returns the supported categories. Categories are strings that serve as tags to identify asset types in a data model-independent fashion.
IsTerminalConfigurationSupported() Returns whether or not the GetTerminalConfiguration() method is applicable.
GetTerminalConfiguration() Returns the terminal configuration for this asset type.

Network attributes

Network attributes are values that are stored for each row in the underlying network topology. The values are read from database attributes and are stored in the topology when it is validated. They can then be accessed directly from the topology without requiring an additional fetch of the corresponding database row, resulting in significant performance benefits when used with tracing. The same network attribute can be assigned to different tables. Some example network attributes are life cycle status, phase, and valve position.

Network attribute objects can be obtained with the GetNetworkAttribute and GetNetworkAttributes methods on the UtilityNetworkDefinition class. GetNetworkAttribute returns a single network attribute by name, while GetNetworkAttributes returns a list of all the network attributes. They provide a number of properties that describe the network attribute. The Assignments property returns a list of NetworkAttributeAssignment objects. The NetworkAttributeAssignment class provides a mapping between network attributes and database fields. The Type property specifies the data type of the network attribute, NetworkAttributeDataType, which has Date, Double, Long, and Short types.

Terminals

A terminal configuration can be assigned to one or more asset types. The TerminalConfiguration objects in a system can be obtained using the GetTerminalConfigurations() method on the UtilityNetworkDefinition class. The GetTerminalConfiguration() method on an AssetType object returns the terminal configuration assigned to that specific asset type.

The TerminalConfiguration class includes a Terminals property that returns a list of terminals.

Each terminal is defined by an ID, a name, and an IsUpstreamTerminal property. This property returns whether this terminal is on the upstream side of a device. As you might expect, in source-based networks, upstream is toward the subnetwork controller. In sink-based networks, upstream is away from the subnetwork controller.

Configuration Paths

Terminal configurations can contain one or more configuration paths, which specify a flow path through terminals. Configuration paths can only be specified for devices with fewer than five terminals. Configuration paths can specify whether flow through them is one-way or bidirectional.

Each configuration path contains a set of TerminalPath objects, which designate a flow path between a pair of terminals. If the Description field is set to the hard-coded value "All", the TerminalPaths parameter will be empty, but there is an implied set of flow paths linking each terminal to every other terminal in the device.

Consider the example of a bypass switch, which is used in conjunction with an electrical device such as a voltage regulator. In normal operation, the blades inside of a bypass switch are configured to allow power to flow through the voltage regulator (represented below as a circle with an 'x').

When the voltage regulator is taken out of service, the disconnect blades are opened and the bypass blade is closed, isolating the voltage regulator from electrical current.

Drawn graphically, the terminal configuration looks like the following, where the green circles are the terminals on the bypass switch and the gray circles represent the voltage regulator.

Using pseudo-code, the TerminalConfiguration object looks like the following:

Name = “Bypass Switch”
Terminals = { terminalA, terminalB, terminalC, terminalD }
ValidConfigurationPaths = { NormalOperation, Bypass }
DefaultConfigationPath = NormalOperation

The NormalOperation configuration path looks like the following:

Name = NormalOperation
TerminalPaths =
  { terminalA, terminalC },
  { terminalD, terminalB }

When in this configuration, terminalA is connected to terminalC and terminalB is connected to terminalD.

For Bypass, the configuration path looks like the following:

Name = ”Bypass”
TerminalPaths = 
  { terminalA, terminalB }

When in this configuration, terminalA is connected to terminalB.

The ConfigurationPath value for a given Device feature is stored in the TerminalConfiguration field on that feature, and is edited using normal geodatabase editing routines.

Rules

Rules define the associations that can be created from the different asset types in a utility network. There are three broad types of association rules:

  • Connectivity rules enforce the types of rows that can be connected.
  • Structural attachment rules enforce the types of rows that can be attached to a structure.
  • Containment rules enforce the types of network rows that can be contained in container rows.

The UtilityNetworkDefinition.GetRules() method returns a list of rules.

Rules are composed of two or three rule element objects, which are accessed through the RuleElements property. The meanings of the rule elements vary depending on the type of rule:

Type of Rule Meaning of Rule Elements
Junction-Junction Connectivity The first rule element represents the from junction. The second rule element represents the to junction. For both of them, the Terminal property is optional.
Junction-Edge Connectivity The first rule element represents the junction, with an optional Terminal property. The second rule element represents the edge (Terminal property is undefined).
Edge-Junction-Edge Connectivity The first rule element represents the first edge. If the Terminal property is present, it indicates the terminal on the junction that the first edge can connect to. The second rule element represents the second edge. If the Terminal property is present, it indicates the terminal on the junction that the second edge can connect to. The third rule element represents the junction (Terminal property is undefined).
Containment The first rule element represents the container, and the second rule element represents the content. The Terminal property on both objects is undefined.
Structural Attachment The first rule element represents the structure, and the second rule element represents the attachment. The Terminal property on both objects is undefined.

Element class

The Element class represents a row in a utility network, plus a terminal (if applicable). These value objects are used across the utility network SDK. Places where Element is used are as follows:

  • Elements are used to create and delete associations.
  • Elements specify starting points and barriers for use with tracing.
  • Elements are returned as results from traces.

Elements are created using the CreateElement() factory methods on the UtilityNetwork class.

If an Element object represents an edge and is being used as input for a trace, the PercentAlongEdge property can be used to specify the exact starting point along the edge (0.5 represents a starting point 50% along the edge, starting at the to junction).

When creating a utility network feature, you should always specify which subtype to use when creating the row buffer. This will ensure the row is initialized with all the expected default values for that subtype. Failure to do so may result in unexpected behavior with attribute rules, contingent values, or other configurations that expect default values for specific subtypes.

using(RowBuffer lineRowBuffer = table.CreateRowBuffer(mediumVoltageSubtype))
{
  lineRowBuffer["Name"] = "Overhead Line";
  
  using(Row lineRow = table.CreateRow(lineRowBuffer))
  {
    Element lineElement = utilityNetwork.CreateElement(lineRow);
  }
}

The ObjectID property is only set if the Element is created from a Row or is returned from a trace. Otherwise the property returns -1. Likewise, calling the CreateElement overrides that do not specify a terminal will create an Element object without a terminal (no default is set).

The FeatureElement represents a row inside a utility network returned from a trace operation with the Features result type. It is not only comprised of Element properties but also includes network and field attributes.

Fetching a row from an element

It is often necessary to obtain a Row from an Element. For example, if a trace returns a collection of elements, you may need to fetch the individual rows.

This is accomplished through the following steps:

  1. Obtain a NetworkSource from the Element.NetworkSource property.
  2. Obtain a Table using UtilityNetwork.GetTable(), passing in the NetworkSource.
  3. Fetch the Row from the Table using a standard QueryFilter and the Element.GlobalID property.

This is demonstrated through the following code snippet:

public static Row FetchRowFromElement(UtilityNetwork utilityNetwork, Element element)
{
  // Get the table from the element
  using (Table table = utilityNetwork.GetTable(element.NetworkSource))
  using (TableDefinition tableDefinition = table.GetDefinition())
  {
    // Create a query filter to fetch the appropriate row
    QueryFilter queryFilter = new QueryFilter()
    {
      WhereClause = tableDefinition.GetGlobalIDField() + " = {" + element.GlobalID.ToString().ToUpper() + "}"
    };

    // Fetch and return the row
    using (RowCursor rowCursor = table.Search(queryFilter))
    {
      if (rowCursor.MoveNext())
      {
        return rowCursor.Current;
      }
      return null;
    }
  }
}

Network topology

The network topology stores connectivity, containment, and attachment information used by the utility network to facilitate fast network traversal and analytical operations. Network topology is constructed from geometric coincidence of features and associations in combination with a powerful rules engine.

Information about the state of the network topology can be returned by calling the GetState() method on the UtilityNetwork class.

Topology is updated and validated with the ValidateNetworkTopology method or ValidateNetworkTopologyInEditOperation extension method on the UtilityNetwork class.

For a CoreHost application, use the ValidateNetworkTopology base method on the UtilityNetwork class. This routine provides its own transaction management; therefore, it cannot be issued inside an edit operation created by Geodatabase.ApplyEdits().

Use the extension method for the Pro SDK add-in. If validation succeeds, it refreshes map views and creates an entry on the Pro operation stack.

The ValidationResult class describes the result of the validate network topology operation. It contains a set of discovered subnetworks that is marked as dirty during ValidateNetworkTopology() operation.

There are a number of different overloads of this method:

Base Method Extension Behavior
ValidateNetworkTopology() ValidateNetworkTopologyInEditOperation(UtilityNetwork utilityNetwork) Validates the entire topology. The asynchronous version of the service is used.
ValidateNetworkTopology(Geometry) ValidateNetworkTopologyInEditOperation(UtilityNetwork utilityNetwork, Geometry extent) Validates the topology within the given extent. Intended for use with small areas, the synchronous version of the service is used.
ValidateNetworkTopology(Geometry, ServiceSynchronizationType) ValidateNetworkTopologyInEditOperation(UtilityNetwork utilityNetwork, Geometry extent, ServiceSynchronizationType serviceSynchronizationType ) Providing complete control for the application developer, this version validates the topology within the given extent (pass in null for the entire extent) using the endpoint version specified.
ValidateNetworkTopology(Geometry, ServiceSynchronizationType, ValidationType)             N/A       Validates the utility network topology within the provided extent according to the provided ValidationType

See the section Synchronous and Asynchronous Services for more information on the different service synchronization types. If using a file or mobile geodatabase, the ServiceSynchronizationType is ignored.

Notably, fine-grained access to the topology network is not provided. For instance, you cannot traverse through a network one element at a time. Each operation would result in a query to the service, and performance and scalability would prove unacceptable. Instead, the utility network provides a powerful tracing framework that removes the need for this kind of traversal.

A future version of the utility network SDK may provide limited query capability against the network topology. Callers may be able to determine what rows are directly connected, attached, or contained to or from an input row. This method may have applicability for some special case workflows and tools. It is not intended to be used repeatedly to traverse through a network and will perform poorly if used in this way.

Associations

The UtilityNetwork class contains methods that query and edit associations. For editing, the UtilityNetwork class provides methods to create and delete associations. These methods should be wrapped in a transaction like other low-level editing methods.

Associations are editing by using the AddAssociation() and DeleteAssociation() methods on the UtilityNetwork class. These methods take an Association object as a parameter.

The meaning of the FromElement and ToElement properties is described in the table below.

Association Type FromElement semantics ToElement semantics
Containment Represents the container Represents the content
Structural Attachment Represents the structure Represents the attached item
Non-spatial Connectivity Represents the junction or non-spatial junction object Represents the non-spatial edge object

The IsContainmentVisible property is only used with containment associations, and the the PercentAlong property is only used with junction-edge object midspan connectivity associations.

Although ideal for stand-alone applications, using these low-level methods are not recommended in an ArcGIS Pro Add-in. The map is not refreshed by these methods, and the undo/redo stack is not modified. See the Editor integration section for higher-level editing methods designed to integrate with the Pro editor.

The GetAssociations() method on the UtilityNetwork class allows associations to be returned, given a particular element.

If you wish to retrieve a set of geometries that represent the associations within a given map extent, the GetAssociationFeatures() method on the UtilityNetwork class can be used. This routine only returns geometries from connectivity associations and structural attachment associations.

Querying associations vs. querying topology

Associations are only one of the building blocks that are used to build network topology. Consequently, associations do not form an accurate or complete view of the topology on their own. For instance, features that are connected by geometric coincidence are not returned by association queries. In addition, association queries can return associations that have not yet been validated and are therefore not yet included in the topological index. Conversely, an association query will not return deleted records that still exist in the topological index. However, querying associations is the correct mechanism to use when building an editing tool, as they show the current edited state of the database.

Edge-junction terminal connectivity

The utility network also allows edges to be connected directly to junction feature terminals. These edge-junction terminal connections are not defined through associations. Instead, they are defined through geometric coincidence of a line's end point with a junction. In addition, the FROMTERMINALID field on the line is used to store the terminal ID of the junction feature that intersects the first point of the line. Likewise, the TOTERMINALID field on the line is used to store the terminal ID of a junction feature that intersects the last point of the line. These fields should be edited using normal geodatabase editing routines.

Association Traversal

Certain data models make extensive use of non-spatial objects and associations. A telecommunications splice junction, for example, can contain hundreds or thousands of internal junctions, edges, and associations. In these cases, querying associations one at a time can get prohibitively expensive. The TraverseAssociations() method on the UtilityNetwork class is provided to handle these use cases. The method can work with multiple input elements, and can traverse up and down through a graph of associations to return multiple objects. Going back to the telecommunications splice junction example, this routine can start with the splice feature and traverse through all of the containment associations to return the hundreds of objects that are contained within the splice- in a single call.

The TraverseAssoctions() method in the UtilityNetwork class provides the information about associations and the field values of elements involved in a traverse associations operation. It takes a set of starting elements and TraverseAssociationsDescription object as parameters to initiate a traverse associations operation.

The TraverseAssociationsDescription describes a direction (ascending or descending) and the depth for a traverse associations operation. The depth value determines the maximum number of hops through the association graph to traverse in a specified direction. By default, the depth is set to maximum for full traversal.

// Upward traversal with maximum depth
TraverseAssociationsDescription traverseAssociationsDescription = new TraverseAssociationsDescription(TraversalDirection.Ascending)
      

// Downward traversal with maximum depth level of 3
TraverseAssociationsDescription traverseAssociationsDescription = new TraverseAssociationsDescription(TraversalDirection.Descending, 3)

Descending in this context includes going from a container to content, going from a structure to a structural attachment, or from the From side of a connectivity association to the To side. Ascending can be used to traverse from content to containers, structural attachments to structures, or from the To side of a connectivity association to the From side. In a junction-edge association, descending is defined as traversing from the junction to the edge.

The TraverseAssociationsDescription has an AdditionalFields property that represents a list of additional field names whose values are returned in the result as field name-value pairs of elements. This allows additional data about the objects to be returned without the need for additional queries.

// List of fields whose values will be returned as name-values pairs during association traversal operation
List<string> additionalFieldsToFetch = new List<string> {“OID", "DeviceName", "AssetType", "AssetName", "AssetGroup”}

TraverseAssociationsDescription traverseAssociationsDescription = new TraverseAssociationsDescription(TraversalDirection.Ascending, 2)
{
AdditionalFields = additionalFieldsToFetch
};

The TraverseAssociationsResult class represents a collection of associations and the mapping between involved elements and their field name-values.

// Get traverse associations result from the staring element up the depth defined in traverseAssociationsDescription
TraverseAssociationsResult traverseAssociationsResult = utilityNetwork.TraverseAssociations(startElements, traverseAssociationsDescription);

// List of associations participated in traversal
IReadOnlyList<Association> associations = traverseAssociationsResult.Associations;

// KeyValue mapping between involved elements and their field name-values 
IReadOnlyDictionary<Element, IReadOnlyList<FieldValue>> associationElementValuePairs = traverseAssociationsResult.AdditionalFieldValues;

The FieldValue has FieldName, FieldType, and Value properties to represent an element’s field name-value pair involved in an association.

Association traversal requires ArcGIS Enterprise 9.0 or higher, and ArcGIS Pro 2.9 or higher.

Subnetworks

Good network management depends on the reliability of paths in a network. The management of these paths allows organizations to optimize the delivery of resources and track the status of a network. In utility networks, paths are referred to as subnetworks. Subnetworks are used to model constructs such as a circuit in electric networks and a zone in gas and water networks. More information about subnetwork concepts can be found in the Subnetwork Management section of the online help.

Each tier of a domain network contains a set of subnetworks. Subnetworks are defined by a collection of one or more subnetwork controllers. Subnetwork controllers serve as either sources or sinks, depending on the definition of the domain network. Each tier defines a set of device asset types that can be designated as a subnetwork controller. For example, a medium-voltage electrical network might use circuit breakers as subnetwork controllers.

The SubnetworkManager class contains a collection of subnetwork management methods. This object is obtained by calling the GetSubnetworkManager() method on the UtilityNetwork class.

Life cycle of subnetworks and subnetwork controllers

As subnetworks are created, edited, and deleted, they transition between a complex series of states. It's worth looking at the life cycle of a subnetwork to see how this works.

The examples in this section show how to use the EnableControllerInEditOperation and DisableControllerInEditOperation, two extension methods on the SubnetworkManager class. These routines will refresh map views and create operations on the Pro operation stack. If writing a CoreHost application, the base routines EnableController and DisableController should be used instead.

Subnetworks with only one subnetwork controller

A subnetwork is created by enabling a device as a subnetwork controller. This is accomplished through the EnableControllerInEditOperation() extension method on the SubnetworkManager class. This method returns a Subnetwork object with a state of Dirty. EnableControllerInEditOperation takes an Element as input. The Element must specify a terminal. For source-based domain networks, the terminal must not be an upstream terminal (as determined by Terminal.IsUpstreamTerminal()). For sink-based domain networks, the terminal must be an upstream terminal.

The following code sample shows how to create a simple radial subnetwork. Note that you need to give both the controller and the subnetwork a name ("R1"). Most utilities will use the same name for both.

Subnetwork R1 = subnetworkManager.EnableControllerinEditOperation(mediumVoltageTier, elementR1, "R1", "R1", "my description", "my notes");

Updating the subnetwork with the Subnetwork.Update() method changes the state of the subnetwork to Clean. It also creates a corresponding SubnetLine feature (assuming that the subnetwork contains features with one of the asset types defined in the tier definition) and updates subnetwork IDs and other propagated attributes on each network row.

R1.Update();
mapView.Redraw(true);

Editing rows in a subnetwork has no impact on the subnetwork state. However, once the topology is validated, the appropriate subnetworks are moved to the Dirty state. Again, the Subnetwork.Update() method is used to return the subnetwork to the Clean state.

At some point, a subnetwork will need to be deleted. This is accomplished through a multistep process that starts by calling the DisableControllerInEditOperation() extension method on the SubnetworkManager class. This moves the subnetwork to the DirtyAndDeleted state and marks the subnetwork controller as deleted.

subnetworkManager.DisableControllerInEditOperation(elementR1);

At this point, the subnetwork is deleted, but all of the rows that have been labeled with the subnetwork ID need to be updated. Once again, Subnetwork.Update() is called. This moves the subnetwork to the CleanAndDeleted state.

subnetworkR1.Update();
mapView.Redraw(true);

At certain points in a utility's business processes, they will want to export their subnetworks to a third-party system such as an outage management system (OMS) or a distribution management system (DMS). The Subnetwork.Export method is used to export a subnetwork. The export operation generates a JSON file based on the options described in the SubnetworkExportOptions class, and saves the resulting JSON at the provided path URI.

Finally, external systems must be updated, and this is accomplished by setting SetAcknowledged to true in the SubnetworkExportOptions. This physically deletes the subnetwork rows from the database and leaves the subnetwork in a Deleted state. At this point, calling further methods and properties on the Subnetwork object will throw an exception.

Set SetAcknowledged to true during a subnetwork export to delete the record of any subnetwork controllers from the subnetworks table that have been removed as a subnetwork controller. The SetAcknowledged is not necessary if there are no controllers that need to be deleted from the subnetworks table.

IReadOnlyList<NetworkAttribute> networkAttributes = utilityNetworkDefinition.GetNetworkAttributes();
IReadOnlyList<NetworkSource> networkSources = utilityNetworkDefinition.GetNetworkSources();

NetworkSource electricDevice = networkSources.First(f => f.Name.Contains("ElectricDevice"));

// Export options
SubnetworkExportOptions subnetworkExportOptions = new SubnetworkExportOptions()
{
  SetAcknowledged = false,
  IncludeDomainDescriptions = true,
  IncludeGeometry = true,
  ServiceSynchronizationType = ServiceSynchronizationType.Asynchronous,

  SubnetworkExportResultTypes = new List<SubnetworkExportResultType>()
  {
	SubnetworkExportResultType.Connectivity,
	SubnetworkExportResultType.Features
  },

  ResultNetworkAttributes = new List<NetworkAttribute>(networkAttributes),

  ResultFieldsByNetworkSourceID = new Dictionary<int, List<string>>()
	{ { electricDevice.ID, new List<string>() { "AssetID" } } }
};

// Execute export
subnetwork.Export(exportResultJsonPath, subnetworkExportOptions);

The following diagram shows the different subnetwork states and the operations used to move between states:

Subnetworks with multiple subnetwork controllers

Some kinds of subnetworks involve multiple controllers. One example is a mesh network in an electrical grid, which is defined by multiple network protector features.

To set this up, the caller will call EnableControllerInEditOperation() for each subnetwork controller, passing in the same subnetwork name each time. Each controller must be given a unique name.

subnetworkManager.EnableControllerInEditOperation(mediumVoltageMeshTier, elementM1, "Esri Campus Mesh", "M1", "my description", "my notes");
subnetworkManager.EnableControllerInEditOperation(mediumVoltageMeshTier, elementM2, "Esri Campus Mesh", "M2", "my description", "my notes");
subnetworkManager.EnableControllerInEditOperation(mediumVoltageMeshTier, elementM3, "Esri Campus Mesh", "M3", "my description", "my notes");
Subnetwork meshSubnetwork = subnetworkManager.EnableControllerInEditOperation(mediumVoltageMeshTier, elementM4, "Esri Campus Mesh", "M4", "my description", "my notes");
meshSubnetwork.Update();
mapView.Redraw(true);

The life cycle generally works as described above. The main exception is that disabling a subnetwork controller will mark the controller as deleted but will not move the subnetwork to the DirtyAndDeleted state unless it is the last remaining controller on that subnetwork.

Advanced case: Multifeed radial networks

Many utilities prefer to name their multifeed subnetworks by concatenating the names of the control devices that feed it. This must be done manually by passing in “R2, R3” as an input argument as shown below.

subnetworkManager.EnableControllerInEditOperation(mediumVoltageMultifeedTier, elementR2, "R2, R3", "R2", "my description", "my notes");
subnetworkManager.EnableControllerInEditOperation(mediumVoltageMultifeedTier, elementR3, "R2, R3", "R3", "my description", "my notes");

If a multifeed network is split into two, the original subnetwork controllers must be disabled and re-enabled with different names.

subnetworkManager.DisableControllerInEditOperation(elementR2);
subnetworkManager.DisableControllerInEditOperation(elementR3);

Subnetwork subnetworkR2 = subnetworkManager.EnableControllerInEditOperation(mediumVoltageTier, elementR2, "R2", "R2", "my description", "mynotes");

Subnetwork subnetworkR3 = subnetworkManager.EnableControllerInEditOperation(mediumVoltageTier, elementR3, "R3", "R3", "my description", "mynotes");

subnetworkR2.Update();
subnetworkR3.Update();
mapView.Redraw(true);

Summary of subnetwork states

Value Description
Dirty Changes have been made to rows in the subnetwork.
Clean Update() has been run subsequent to any edits made to the subnetwork.
CleanAndAcknowledged Export with the SetAcknowledged flag has been run after Update() and before additional edits.
DirtyAndDeleted All subnetwork controllers have been dropped from the subnetwork, but Update() has not yet been run.
CleanAndDeleted All subnetwork controllers have been dropped from the subnetwork, Update() has been run, but Export() has not been run with the SetAcknowledged flag. Once a subnetwork is in this state, running Export() with the SetAcknowledged flag will delete the subnetwork.
InvalidSubnetworkObject The subnetwork has been completely deleted (no database rows exist), but the subnetwork object remains. Once a subnetwork is in this state, most properties and methods will throw an exception.
InvalidWithErrors The subnetwork has been updated, and an error condition was discovered. Once the subnetwork is in this state, it cannot be updated until edits have been made to correct the error condition and validation has been run successfully.
All A combination of the values in this list; provides an easy way to fetch all of the subnetworks in a tier.

Subnetworks and transaction management

The SubnetworkManager.EnableControllerInEditOperation() and SubnetworkManager.DisableControllerInEditOperation() extension methods perform their own transaction management. Because of this, they cannot be called from within another edit operation. In a CoreHost application the base EnableController() and DisableController() methods also perform their own transaction management. If called inside Getodatabase.ApplyEdits() an exception will be thrown.

The Subnetwork.Update() method has the same restrictions, and one more. Because the edits produced by this method cannot be undone, it cannot be called from within an edit transaction. All edits must be saved or reverted prior to calling this method.

Similar transaction restriction applies to the subnetwork export. When calling Subnetwork.Export with SubnetworkOptions.SetAcknowledged as True inside Geodatabase.ApplyEdits(), an exception will be thrown.

Enabling and disabling controllers create dirty areas; updating a subnetwork does not.

As shown in the examples above, after updating a subnetwork, ArcGIS.Desktop.Mapping.MapView.Redraw(true) should be called to refresh the display cache and redraw the map.

Updating All Dirty Subnetworks

In addition to updating subnetworks individually with Subnetwork.Update(), the API also allows all dirty subnetworks to be updated at once using the SubnetworkManager.UpdateAllSubnetworks() method. This routine takes a tier and a boolean that specifies whether processing should continue on subsequent subnetworks if a failure is encountered.

The following code example shows how to update all of the dirty subnetworks in a tier:

using (SubnetworkManager subnetworkManager = utilityNetwork.GetSubnetworkManager())
{
  subnetworkManager.UpdateAllSubnetworks(tier, true);

  mapView.Redraw(true);
}

Retrieving subnetworks

Existing subnetworks can be retrieved by calling the GetSubnetworks() method on the SubnetworkManager object. The method takes a tier parameter, as well as a SubnetworkStates value, which can include multiple values or'd together. This method returns a list of subnetwork objects. Alternatively, the GetSubnetwork() method can be called. This method takes a string, which represents a subnetwork name.

In addition to a number of properties, the Subnetwork class also contains the Update() method described above. Other methods include GetControllers(), which returns a list of subnetwork controller objects, and GetLineFeature(), which returns the SubnetLine feature that corresponds to this subnetwork.

Tracing

Tracing entails identifying a subset of utility network elements that meet a specified criteria. Tracing uses network data to provide business value to utilities. Tracing also helps you do the following:

  • Answer questions and solve problems about the current state of the network
    • What valves need to be opened to shut off gas to this location?
    • If these three houses lost power during a storm, which device is responsible?
  • Design future facilities
    • How many houses are fed by this transformer and can the equipment handle another connection?
  • Organize business practices
    • How can I create a circuit map to give to my work crews for damage assessment after an ice storm?

Architecture of a trace

Many different classes work together to define and execute a trace:

Tracers

Tracers define the tracing algorithm to be used. Tracer is an abstract base class with distinct subclasses implementing specific tracing algorithms.

Tracer objects are created using TraceManager. The TraceManager class is a central hub to the tracing portions of the API. TraceManager objects are obtained through a call to UtilityNetwork.GetTraceManager().

The GetTracer<T>() method on the TraceManager class returns a tracer of a specific type as shown in the following code:

using (TraceManager traceManager = utilityNetwork.GetTraceManager())
{
  DownstreamTracer downstreamTracer = traceManager.GetTracer<DownstreamTracer>();
}

Likewise, the GetTracer(NamedTraceConfiguration) method on the TraceManager class returns a tracer based on the NamedTraceConfiguration as shown below.

using (TraceManager traceManager = utilityNetwork.GetTraceManager())
{
	NamedTraceConfigurationQuery namedTraceConfigurationQuery = new NamedTraceConfigurationQuery { Names = new List<string> { "Upstream"} };
	
	IReadOnlyList<NamedTraceConfiguration> namedTraceConfigurations = traceManager.GetNamedTraceConfigurations(namedTraceConfigurationQuery);
	
	NamedTraceConfiguration upstreamTraceConfiguration = namedTraceConfigurations.FirstOrDefault();
	
	Tracer upstreamTracer = traceManager.GetTracer(upstreamTraceConfiguration);
} 

Extending Tracer

Although most tracing functionality can be built using the configuration framework, there are other cases where clients will want a custom tracer.

Custom tracers could wrap Esri tracers. For example, a partner might provide a Tracer object that automatically includes a set of NetworkAttribute filters to specify phase. Partners may want to provide additional preprocessing or postprocessing that the Esri configuration framework doesn’t provide. Finally, custom tracers could be manually written. For example, a partner might make a call to a DMS or other external system to perform an analytic.

Custom tracers are created by deriving from the Tracer abstract class.

Trace arguments

The TraceArgument class consolidates trace parameters. Different tracers, including custom tracers, may subclass TraceArgument. The base TraceArgument class contains properties for starting locations, barriers, filter barriers, result types, and a trace configuration.

Starting locations are Elements that specify the starting point of a trace, while barriers and filter barriers (both also Elements) prevent a trace from continuing.

Barriers work by preventing traversability, which is done during the initial phase of a trace. With subnetwork-based traces, they can prevent traversability to the subnetwork controller. One use case for a barrier from the electric distribution domain would be placing a barrier on a switch to indicate that the switch is open. This would prevent electricity from reaching areas downstream of that switch.

Filter barriers are evaluated in a later pass of the tracing algorithm, and can be used to restrict the returned result set without impacting the ability to find the subnetwork controller from a starting point. One use case for a filter barrier from the gas distribution domain would be using a filter barrier to simulate the location of a squeeze-off when executing a valve isolation trace. The user could place a filter barrier at the proposed location of the squeeze-off and then verify its impact.

In the absence of barrier and filter barrier elements, users would have switch to a version, edit features, and then validate network topology in order to perform this kind of what-if analysis.

The trace configuration is explained later in this topic.

The ResultTypes property allows you to specify one or more result formats to return. Currently implemented values are Element, which returns a set of elements, and FunctionValue, which returns a set of function calculation results.

Future software versions may implement additional result types. Possible result types that may be supported in the future include propagator values per row, connectivity information, network diagrams, geometry, and additional network attributes.

The following snippet shows how to create a TraceArgument object:

IReadOnlyList<Element> startingPointList = new List<Element>();

// Code to fill in list of starting points goes here...

TraceArgument traceArgument = new TraceArgument(startingPointList);

TraceConfiguration traceConfiguration = new TraceConfiguration();

// Code to fill in trace configuration goes here...

traceArgument.Configuration = traceConfiguration;

TraceArgument objects can also be created with a NamedTraceConfiguration.

using (TraceManager traceManager = utilityNetwork.GetTraceManager())
{
  IReadOnlyList<Element> startingPoints = new List<Element>();

  // Returns all named trace configurations
  IReadOnlyList<NamedTraceConfiguration> namedTraceConfigurations = traceManager.GetNamedTraceConfigurations(new NamedTraceConfiguration());

  NamedTraceConfiguration namedTraceConfiguration = namedTraceConfigurations.FirstOrDefault();

  // Initializes a new instace of trace argument object using a named trace configuration and a set of elements
  TraceArgument traceArgument = new TraceArgument(namedTraceConfiguration, startingPoints);
}

SubnetworkTracer can also support a TraceArgument object that is created using a Subnetwork parameter. In this case, the subnetwork's controllers are used as starting points.

Trace configuration

The TraceConfiguration object contains all of the other inputs to a trace.

The properties in the TraceConfiguration object can be categorized as follows:

Basic properties

The following table lists the basic properties in the TraceConfiguration object and their descriptions:

Property Description
AllowIndeterminateFlow Determines whether elements inside a loop are returned with subnetwork trace results. The default is true.
IncludeContainers Includes containers of trace results in the result set. IncludeContainers is recursive. If set to true, if a result element is inside a nested container, both containers are returned. The default is false.
IncludeContent Includes content of trace results in the result set. IncludeContent is recursive. If set to true, if a result element contains other elements that are themselves containers, the contents of the nested containers are also returned. The default is false.
IncludeStructures Includes structures with attachments that are trace results in the result set. The default is false.
IncludeBarriersWithResults Determines whether the row that met one of the barrier criterion (described in the Traversability and Filter sections below) is included in the trace results. For example, it is typical to include an open switch when performing an electric circuit trace, so in these cases, the property should be set to True.
IncludeIsolatedFeatures Determines whether or not the isolated features are returned with trace results. The default is false, which only returns those features that stop the trace (e.g., valves).
IgnoreBarriersAtStartingPoint Determines whether or not barriers at starting points are ignored. One typical use case is performing an upstream trace, stopping at each protective device. Each subsequent trace would use as a starting point the end point of the previous trace. Without this setting, subsequent traces would stop at the starting point, and not return any results. The default is false.
ShortestPathNetworkAttribute This property is only used by ShortestPathTracer and is used by that tracer to determine the shortest path. Typically, shape length is used. However, other network attributes can be used to calculate cost. For example, if you are looking for the shortest path through an underground system, you might prefer a longer path via duct banks versus a shorter path through a trench that requires excavation.
DomainNetwork The DomainNetwork property is required and only used with subnetwork-based tracers.
SourceTier This property is only used with subnetwork-based traces. If DomainNetwork represents a partitioned network, the SourceTier property is optional. If DomainNetwork represents a hierarchical network, the SourceTier property is required. Since rows in hierarchical networks can belong to multiple tiers, this property notifies the tracer which tier to use for tracing. If this property is included, the trace code will perform an additional check to validate that the starting points and barriers belong to this tier.
TargetTier If this optional property is included, upstream and downstream traces will continue into the specified tier. If the property is null, upstream and downstream traces will stop in the current tier (that is, at subnetwork controllers).
UseDigitizedDirection Indicates whether a trace will use the flow direction. The default is False.
ValidateConsistency If set to true, the trace code will perform additional checks. If the trace encounters a dirty area, an exception is thrown from Trace(). The default is True.

IncludeContainers and IncludeStructures are transitive. In other words, if they are both true, if a container contains a result element and that container is attached to a structure, the structure is returned even if the result element is not directly attached to the structure.

Traversability

As the tracer navigates through the network, conditions can be tested to stop traversal. If the visited row meets the criteria of the condition, traversal stops. Traversability is based on the following:

  • Comparison of network attributes or checking for the existence of a Category. These can be combined with Boolean And and Or operations to form more complex filters.
  • Evaluation of a functional expression.

Traversability is defined by a Traversability property on the TraceConfiguration class. This property contains a Traversability object.

The TraversabilityScope property determines whether the criteria are evaluated on edges, junctions, or both.

Conditions

The Condition class is an object that evaluates to true or false. Currently there is a single subclass of Condition called ConditionalExpression.

If the visited row meets the criteria of the comparison, traversal is stopped. If the visited row does not meet the criteria, that row is not included in the results. A null condition always permits traversal.

There are two basic comparisons as follows:

  • The NetworkAttributeComparison class is based on comparison with a network attribute. This network attribute can be compared to a specific non-null value or a second network attribute.

  • The CategoryComparison class checks the asset type of the row to establish if it implements a specific category.

These filters can be combined using the And and Or classes to form more complex filters.

The following code shows how to build a condition that stops when traversal encounters a row with a Lifecycle network attribute that contains a value other than InDesign or InService.

const int InDesign = 4;
const int InService = 8;

using (NetworkAttribute lifecycleNetworkAttribute = utilityNetworkDefinition.GetNetworkAttribute("Lifecycle"))
{
  // Create a NetworkAttributeComparison that stops traversal if Lifecycle <> "In Design" (represented by the constant InDesign)
  NetworkAttributeComparison inDesignNetworkAttributeComparison = new NetworkAttributeComparison(lifecycleNetworkAttribute, Operator.NotEqual, InDesign);

  // Create a NetworkAttributeComparison to stop traversal if Lifecycle <> "In Service" (represented by the constant InService)
  NetworkAttributeComparison inServiceNetworkAttributeComparison = new NetworkAttributeComparison(lifecycleNetworkAttribute, Operator.NotEqual, InService);

  // Combine these two comparisons together with "And"
  And lifecycleFilter = new And(inDesignNetworkAttributeComparison, inServiceNetworkAttributeComparison);

  // Final condition stops traversal if Lifecycle <> "In Design" and Lifecycle <> "In Service"
  traceConfiguration.Traversability.Barriers = lifecycleFilter;
}
Function barriers

The final way to stop traversal is based on the results of a function. The FunctionBarrier class specifies that traversal will stop when the value of a function calculation is compared with a specific value.

As the network is being traversed, the value of Function for the given network row is compared against the Value parameter using the provided Operator. If the comparison is true, further traversal is stopped.

The difference between local and global function values deserves more explanation. The internal tracing algorithms start traversal in an arbitrary direction and perform a breadth-first traversal (this internal implementation is subject to change in the future). Global and local function values are described as follows:

  • When using global function values, a single calculated value is maintained. For example, if you want to trace out a total of 1000 feet in any direction, use global values.
  • When using local values, when the traversal encounters a fork, the calculated values for each branch are maintained separately. For example, if you want to trace out 1000 feet in each direction, use local values.

Here is another example. In the following diagram, the red junction is the starting point. Assume that all lines have a traversal value of 1, and the function barrier specifies that traversal stops when the value equals 2.

Using global values, any of these could be returned depending on how the edges are ordered in the network topology.

Using local values, the following result is returned:

Most applications will use local values. Note that the global value is the single value returned when specifying functions as return values as described in the next section.

The following code example shows how set up a function barrier to stop traversal after 1000 feet:

 // Create a NetworkAttribute object for the Shape length network attribute from the UtilityNetworkDefinition
 using (NetworkAttribute shapeLengthNetworkAttribute = utilityNetworkDefinition.GetNetworkAttribute("Shape length"))
 {
   // Create a function that adds up shape length
   Function lengthFunction = new Add(shapeLengthNetworkAttribute);

   // Create a function barrier that stops traversal after 1000 feet
   FunctionBarrier distanceBarrier = new FunctionBarrier(lengthFunction, Operator.GreaterThan, 1000.0);

   // Set this function barrier
   traceConfiguration.Traversability.FunctionBarriers = new List<FunctionBarrier>() { distanceBarrier };
 }

Functions

The caller can specify a collection of functions for a trace. These functions calculate values based on a network attribute. At the conclusion of the trace, these function results are returned.

Functions are evaluated at each applicable row that has the assigned network attribute. The meaning of "applicable" varies per trace as follows:

  • For an upstream trace, the functions are evaluated for each upstream row.
  • For a downstream trace, the functions are evaluated for each downstream row.
  • For a subnetwork trace, the functions are evaluated for each row in the subnetwork.
  • For a subnetwork controllers trace, the functions are evaluated for each subnetwork controller row.

The features that are used to calculate the value can be further restricted with the Condition property. See the Conditions section above for more information. When used in a FunctionBarrier, the Function.Condition property is ignored.

Note that functions are calculated before output filters are applied.

Individual functions are implemented as subclasses of the Function abstract class.

Function Description
Add Sums the value of the network attribute for each applicable row
Subtract Takes the network attribute value from the starting point as the base number and subtracts the value of the network attribute for each applicable row
Average Averages the value of the network attribute for each applicable row
Count Counts the number of applicable rows
Min Minimum value of the network attribute for each applicable row
Max Maximum value of the network attribute for each applicable row

The following code shows how to sum the values of a Load network attribute and include it in the trace results:

 // Get a NetworkAttribute object for the Load network attribute from the UtilityNetworkDefinition
 using (NetworkAttribute loadNetworkAttribute = utilityNetworkDefinition.GetNetworkAttribute("Load"))
 {
   // Create a function to sum the Load
   Add sumLoadFunction = new Add(loadNetworkAttribute);

   // Add this function to our trace configuration
   traceConfiguration.Functions = new List<Function>() { sumLoadFunction };
 }

Filters

Filters are a mechanism to stop tracing when returning results. They do not stop traversability to the controller.

For example, consider an upstream protective device trace. At first, you might try defining a CategoryComparison that looks for a Protective Device category and assigning this to Traversability.Condition. When you try to run an upstream trace using this configuration, it will probably fail. This is because traversability will stop at the first protective device, and the trace will be unable to find the subnetwork controller. The correct way to implement this trace is to assign CategoryComparison to the filter.

There a number of different ways to define filters. You can use any of the following:

  • Condition based on network attributes or categories
  • Functional expression
  • Bitset filter
  • Nearest neighbor filter

Filters are defined by the TraceConfiguration.Filter property, which is an instance of the Filter class.

The Barriers, Scope, and FunctionBarriers properties work the same way they do with traversability. The other two properties are described in the sections that follow.

Bitset filter

There are cases where traces need to be aware that a network attribute is a bit set that controls traversability. Consider the following network, where phase is represented as a bit set network attribute (one bit per phase), and overhead electrical devices are represented with one device per phase.

If you do an upstream trace from a starting point on the B-phase device with no special filter in place, the following results make sense from a graph-theory perspective. Electrically, of course, this is not the result you are looking for.

Setting the Filter.BitsetNetworkAttribute property allows the trace to return the correct results with upstream, downstream, and loop traces.

Nearest neighbor filter

The nearest neighbor filter allows you to return the next N features from the starting point. A network attribute is used to define the distance. Typically, shape length is used, but a different network attribute representing cost can be used instead. For example, if you are searching for the nearest vault in an underground structure network, you may prefer a geographically distant vault that is connected via a duct bank over a shorter route through a direct-buried trench (since excavating the trench is more costly).

The type of features to be returned can be specified by a set of Category strings, asset types, or both.

The nearest neighbor filter is defined by the NearestNeighbor class, which is assigned to the Filter.NearestNeighbor property.

Output

Trace results can be filtered with conditions or a list of asset types. Output filtering is applied as the final step of the tracing process. It occurs after traversability, filtering, and function calculations.

Output filtering is defined through two properties on TraceConfiguration: OutputCondition and OutputAssetTypes. OutputCondition is the same Condition class used elsewhere in the tracing API. It can be based on comparisons against network attributes, categories, or both. OutputAssetTypes allows a set of asset types to be used to filter. Using CategoryComparison with the OutputCondition property is the preferred technique to avoid hardcoding your add-in to a particular data model as shown below.

// Create an output category to filter the trace results to only include
// features with the "Service Point" category assigned
traceConfiguration.OutputCondition = new CategoryComparison(CategoryOperator.IsEqual, "Service Point");

If both types of output filters are provided, both filters must be satisfied for the row to be included in the result set. If both properties are null, no additional filtering takes place.

Propagators

A propagator defines the propagation of a network attribute along a traversal and provides a filter to stop traversal. Propagators are only applicable to subnetwork-based traces (subnetwork, subnetworksource, upstream, or downstream).

The standard example is electric phase propagation where open devices along the network will restrict some phases from continuing along the trace.

Propagator values are computed as a preprocess step before the main trace occurs. Starting at each subnetwork controller, the propagator uses PropagatorFunction and NetworkAttribute to calculate a value at each element.

PropagatorFunction controls how the attribute is propagated away from the subnetwork controllers. For attributes that correspond to numeric values, a min or max operator can be specified. For attributes represented as bit sets, bitwise set operators can be specified.

This preprocess traversal covers the extent of a subnetwork. During the trace, the FilterOperator is tested at the same time as traversal filters. The filter compares the propagated value with the value provided and stops traversal if false.

The following code sample shows how to set up a phase-based propagator:

const int ABCPhase = 7;

 // Get a NetworkAttribute object for the Phases Normal attribute from the UtilityNetworkDefinition
 using (NetworkAttribute normalPhaseAttribute = utilityNetworkDefinition.GetNetworkAttribute("Phases Normal"))
 {
   // Create a propagator to propagate the Phases Normal attribute downstream from the source, using a Bitwise And function
   // Allow traversal to continue as long as the Phases Normal value includes any of the ABC phases
   // (represented by the constant ABCPhase)
   Propagator phasePropagator = new Propagator(normalPhaseAttribute, PropagatorFunction.BitwiseAnd, Operator.IncludesAny, ABCPhase);

   // Assign this propagator to our trace configuration
   traceConfiguration.Propagators = new List<Propagator>() { phasePropagator };
 }

Named trace configuration

A named trace configuration is a predefined set of trace options to simplify configuring and running a trace operation in the utility network. It allows clients to perform trace analytics without needing to understand the data model. Client applications can simply allow the user to choose from a set of named trace configurations that provide tracing capabilities. The set can be configured for different workflows, and a single application can be used by multiple customers and even across multiple problem domains.

Named traced configurations are built with ArcGIS Pro, typically stored on ArcGIS Enterprise, and shared through web maps.

The named trace configurations associated with a utility network layer can accessed by calling the GetNamedTraceConfigurations() method on the UtilityNetworkLayer class.

IReadOnlyList<NamedTraceConfiguration> GetNamedTraceConfigurationsFromLayer(UtilityNetworkLayer utilityNetworkLayer)
{
  // Returns a set of named traced configurations associated with the layer
  IReadOnlyList<NamedTraceConfiguration> namedTraceConfigurations = utilityNetworkLayer.GetNamedTraceConfigurations();
  
  return namedTraceConfigurations;
}

The named trace configruations can also be obtained from a TraceManager as shown in the following code:

using (TraceManager traceManager = utilityNetwork.GetTraceManager())
{
  // Query to find named trace configurations whose name is 'UpstreamTrace'
  NamedTraceConfigurationQuery namedTraceConfigurationQuery = new NamedTraceConfigurationQuery {Names = new List<string> { “UpstreamTrace” } };

  // A set of named trace configurations specified by the named traced configuration query 
  IReadOnlyList<NamedTraceConfiguration> namedTraceConfigurations = traceManager.GetNamedTraceConfigurations(namedTraceConfigurationQuery);
}

The NamedTraceConfigurationQuery class defines a filter to find named trace configurations based on creators, GlobalIDs, names, or tags.

Trace results

The Trace() method on Tracer classes returns a set of Result objects. One Result object is returned for each ResultType specified in TraceArgument.ResultTypes.

Result is an abstract class. One distinct subclass exists for each member of the ResultTypes enum.

The Result base class has a single property. If the TraceConfiguration.Filter object contained a NearestNeighbor object, the NearestNeighborResult property returns whether or not the correct number of rows were returned.

The current release supports the following subclasses of Result:

  • ElementResult contains a list of Element objects that meet the tracing criteria.
  • FeatureElementResult contains a list of FeatureElement objects that meet the tracing criteria. These objects are used to include network attribute and other field values with the trace result.
  • FunctionOutputResult contains a list of FunctionOutput objects. One FunctionObject is returned for each function assigned to TraceConfiguration.Functions. The FunctionOutput class is a set of function-value pairs.
  • AggegatedGeometryResult contains a list of geometries. The Line property contains the union of all of the linear features included in the result set. Likewise, the Point property contains the union of all of the point features, and the Polygon property contains the union of all of the polygonal features included in the result set. Returning geometries requires ArcGIS Enterprise 10.8.1 or later.

This code shows how to get the result of the function that was created earlier in this topic to sum the Load attribute.

// Get the FunctionOutcomeResult from the trace results
FunctionOutputResult functionOutputResult = traceResults.OfType<FunctionOutputResult>().First();

// First() can be used here because only one Function was included in the TraceConfiguration.Functions collection
FunctionOutput functionOutput = functionOutputResult.FunctionOutputs.First();

// Extract the total load from the GlobalValue property
double totalLoad = (double)functionOutput.Value;

Export trace

The trace results can be exported in the same way you export subnetworks by using the Export() method in the Tracer class. The export operation constructs JSON result based on the parameters defined in the TraceArgument and the TraceExportOptions classes and saves it at the provided path URI. The JSON format allows an easy integration with third-party systems for outage or distribution management.

// Set export options 
TraceExportOptions exportOptions = new TraceExportOptions()
{
  ServiceSynchronizationType = ServiceSynchronizationType.Asynchronous,
  IncludeDomainDescriptions = true,
};

// Path to save trace results
string jsonPath =  $"{Path.GetTempPath()}TraceResults.json";
Uri jsonUri = new Uri(jsonPath);

// Execute export 
downstreamTracer.Export(jsonUri, traceArgument, exportOptions);

string jsonAbsolutePath = HttpUtility.UrlDecode(jsonUri.AbsolutePath);
if (jsonUri.IsFile && File.Exists(jsonAbsolutePath))
{
  // Work with the JSON 
}

Putting it all together

Once the TraceArgument with its TraceConfiguration is assembled, the Trace method on the Tracer object executes the trace and returns results.

The following holistic code sample demonstrates how all of these options can be configured to work together. This trace calculates the downstream load and count of service points on a particular phase of an electric utility network.

public void LoadAndCountPerPhaseTrace(UtilityNetwork utilityNetwork, IReadOnlyList<Element> startingPoints, int phaseValue)
{
  using (TraceManager traceManager = utilityNetwork.GetTraceManager())
  {
    DownstreamTracer downstreamTracer = traceManager.GetTracer<DownstreamTracer>();

    UtilityNetworkDefinition definition = utilityNetwork.GetDefinition();

    // Create comparison to stop traversal if the specified phase isn't found
    NetworkAttribute normalPhasesNetworkAttribute = definition.GetNetworkAttribute("PhasesNormal");
    NetworkAttributeComparison phaseComparison = new NetworkAttributeComparison(normalPhasesNetworkAttribute, Operator.DoesNotIncludeTheValues, phaseValue);

    // Create comparison to stop traversal at open devices
    NetworkAttribute deviceStatusAttribute = definition.GetNetworkAttribute("Device status");
    NetworkAttributeComparison deviceStatusComparison = new NetworkAttributeComparison(deviceStatusAttribute, Operator.Equal, DeviceStatusOpen);

    // Combine these two comparisons and assign it
    Condition terminationCondition = new Or(phaseComparison, deviceStatusComparison);

    // Create function to add up loads on service points
    NetworkAttribute loadNetworkAttribute = definition.GetNetworkAttribute("Load");
    Function sumServicePointLoadFunction = new Add(loadNetworkAttribute);

    // Create Trace Configuration object
    TraceConfiguration traceConfiguration = new TraceConfiguration();
    traceConfiguration.Traversability.Barriers = terminationCondition;
    traceConfiguration.Functions = new List<Function>() { sumServicePointLoadFunction };
    traceConfiguration.OutputCondition = new CategoryComparison(CategoryOperator.IsEqual, "ServicePoint");
    
    // Execute the trace
    TraceArgument traceArgument = new TraceArgument(startingPoints);
    traceArgument.Configuration = traceConfiguration;
    IReadOnlyList<Result> traceResults = downstreamTracer.Trace(traceArgument);

    // Output results
    ElementResult elementResult = traceResults.OfType<ElementResult>().First();
    int countCustomers = elementResult.Elements.Count;

    FunctionOutputResult functionOutputResult = traceResults.OfType<FunctionOutputResult>().First();
    FunctionOutput functionOutput = functionOutputResult.FunctionOutputs.First();
    int sumLoad = (int)functionOutput.Value;

    Console.WriteLine("Number of customers assigned to phase: " + countCustomers);
    Console.WriteLine("Total load for this phase: " + sumLoad);    
  }
}

TraceConfiguration class in other contexts

In addition to its use with tracing, the TraceConfiguration class is used in other places in the API. This section describes how its use differs from tracing.

TraceConfiguration on the Tier class

A TraceConfiguration is obtained from the GetTraceConfiguration method on the Tier class. It is assigned by the Set Subnetwork Definition geoprocessing tool where it defines the defaults used by subnetwork traces, updates, and exports.

Not all of the TraceConfiguration properties apply in this scenario. Nonapplicable properties are left empty as shown in the following table:

Property Applicable
DomainNetwork
Filter
Functions
IgnoreBarriersAtStartingPoint
IncludeBarriersWithResults
IncludeContainers
IncludeContent
IncludeIsolatedFeatures
IncludeStructures
OutputAssetTypes
OutputCondition
Propagators
ShortestPathNetworkAttribute
SourceTier
TargetTier
Traversability
ValidateConsistency

Propagated values are stored on domain network feature classes during the Update Subnetwork process. The field used is designated by the Propagator.PersistedField property.

Similarly, the Function property is used to store subnetwork summaries. These are computed during Update Subnetwork and stored on the SubnetLine feature class. The field used is designated by the Function.PersistedField property.

TraceConfiguration with Subnetwork.Update()

Subnetwork.Update() typically uses the default tracing configuration represented by Tier.GetTraceConfiguration(). However, an override of Subnetwork.Update() allows this default trace configuration to be overridden.

Not all the TraceConfiguration properties apply in this scenario as shown in the following table below:

Property Applicable
DomainNetwork
Filter
Functions
IgnoreBarriersAtStartingPoint
IncludeBarriersWithResults
IncludeContainers
IncludeContent
IncludeStructures
OutputAssetTypes
OutputCondition
Propagators
ShortestPathNetworkAttribute
SourceTier
TargetTier
Traversability
ValidateConsistency

Pro integration

While the utility network API resides in the ArcGIS.Core.Data namespace, there are places where you must integrate utility network code with ArcGIS Pro. These areas are described in the sections below.

Utility network layers

The UtilityNetworkLayer class represents a utility network layer in a map. This class resides in a mapping rather than a geodatabase namespace, ArcGIS.Desktop.Mapping.

The GetUtilityNetwork() method returns the UtilityNetwork class pointed at by this layer. This can be used with ArcGIS Pro add-ins to obtain the underlying geodatabase object from the selected layer as demonstrated in the following code:

public static UtilityNetwork GetUtilityNetworkFromLayer(Layer layer)
{
  UtilityNetwork utilityNetwork = null;

  if (layer is UtilityNetworkLayer)
  {
    UtilityNetworkLayer utilityNetworkLayer = layer as UtilityNetworkLayer;
    utilityNetwork = utilityNetworkLayer.GetUtilityNetwork();
  }

  else if (layer is SubtypeGroupLayer)
  {
    CompositeLayer compositeLayer = layer as CompositeLayer;
    utilityNetwork =  GetUtilityNetworkFromLayer(compositeLayer.Layers.First());
  }

  else if (layer is FeatureLayer)
  {
    FeatureLayer featureLayer = layer as FeatureLayer;
    using (FeatureClass featureClass = featureLayer.GetFeatureClass())
    {
      if (featureClass.IsControllerDatasetSupported())
      {
        IReadOnlyList<Dataset> controllerDatasets = new List<Dataset>();
        controllerDatasets = featureClass.GetControllerDatasets();
        foreach (Dataset controllerDataset in controllerDatasets)
        {
          if (controllerDataset is UtilityNetwork)
          {
            utilityNetwork = controllerDataset as UtilityNetwork;
          }
          else
          {
            controllerDataset.Dispose();
          }
        }
      }
    }
  }
  return utilityNetwork;
}

The UtilityNetworkLayer class inherits from CompositeLayer, which can be used to navigate to the dirty area and error sublayers.

Ribbon integration

To create a button on the ribbon that works with utility network layers, use the utility network condition in your DAML as follows:

condition="esri_mapping_utilityNetworkCondition"

This will cause the button to be enabled if a utility network is present in the map.

This behavior changed at Pro 2.3. Prior to Pro 2.3, a different condition (esri_mapping_utilityNetworkLayerSelectedCondition) would only enable your button if any of the following layers are selected:

  • Utility network layer
  • Feature layer that corresponds to a feature class that belongs to a utility network
  • Subtype group layer that corresponds to a feature class that belongs to a utility network

This old condition has been removed, and we recommend revisiting your code to make sure that it will continue to work with the new contract. If your code continues to use esri_mapping_utilityNetworkLayerSelectedCondition, your button will always be enabled with Pro 2.3 and later releases.

For more information on creating a button or other add-in, see the conceptual documentation.

Editor integration

This section includes information about how to edit utility networks in ArcGIS Pro. It is highly recommended that you review the Editor conceptual documentation before continuing with this section. Rather than being included in a geodatabase namespace, the classes introduced in this section belong to ArcGIS.Desktop.Editing.

Editing associations with EditOperation

The EditOperation class has been extended with methods to create and delete associations. New overloads to EditOperation.Create() and EditOperation.Delete() take association description objects. These objects describe a connectivity, containment, or structure attachment association to create or delete.

As shown in the class diagram above, association description objects are created with RowHandle objects. The RowHandle class describes a row that is involved in the creation or deletion of an association. Row handles can be created using the following inputs:

  • UtilityNetwork and Element
  • MapMember and GlobalID
  • MapMember and ObjectID
  • Row
  • RowToken (described in the following section)

Creating an association description with RowHandle requires the global IDs of both features. Creating a RowHandle with only an ObjectID could cause an additional query to fetch this GlobalID. Therefore, using the other constructors is preferred.

The following code snippet demonstrates how to build a structural attachment association between a pole and a transformer bank:

// Create edit operation

EditOperation editOperation = new EditOperation();
editOperation.Name = "Create structural attachment association";

// Create a RowHandle for the pole

Element poleElement = utilityNetwork.CreateElement(poleAssetType, poleGlobalID);
RowHandle poleRowHandle = new RowHandle(poleElement, utilityNetwork);

// Create a RowHandle for the transformer bank

Element transformerBankElement = utilityNetwork.CreateElement(transformerBankAssetType, transformerBankGlobalID);
RowHandle transformerBankRowHandle = new RowHandle(transformerBankElement, utilityNetwork);

// Attach the transformer bank to the pole

AssociationDescription structuralAttachmentAssociationDescription = new AssociationDescription(AssociationType.Attachment, poleRowHandle, transformerBankRowHandle);
editOperation.Create(structuralAttachmentAssociationDescription);
editOperation.Execute();

There are many different constructors for the AssociationDescription class. The following table shows which constructors can be used with each association type.

Association Type Constructors
Containment AssociationDescription(AssociationType, RowHandle, RowHandle, isContainmentVisible: bool)
AssociationDescription(AssociationType, RowHandle, RowHandle)
Sets containment to be invisible
Structural Attachment AssociationDescription(AssociationType, RowHandle, RowHandle)
Junction-Junction AssociationDescription(AssociationType, RowHandle, RowHandle)
AssociationDescription(AssociationType, RowHandle, terminalID1: long, RowHandle)
The first junction uses terminals
AssociationDescription(AssociationType, RowHandle, RowHandle, terminalID2: long)
The second junction uses terminals
AssociationDescription(AssociationType, RowHandle, terminalID1: long, RowHandle, terminalID2: long)
Both junctions use terminals
Junction-Edge Object Connectivity From Side
Junction-Edge Object Connectivity To Side
AssociationDescription(AssociationType, RowHandle, RowHandle)
AssociationDescription(AssociationType, RowHandle, terminalID1: long, RowHandle)
The first junction uses terminals. Junction-edge object connectivity associations always specify the junction first
Junction-Edge Object Connectivity Midspan AssociationDescription(AssociationType, RowHandle, RowHandle, percentAlong: double)
Midspan junction-edge object connectivity associations do not support terminals. The percentAlong value should be between 0.0 and 1.0 (exclusive)

Creating features and associations in the same edit operation

It is often desirable to create features and the associations between them in the same edit operation. This ensures that only one entry is created in the undo/redo stack and may result in fewer calls to the feature service. At first glance, it might appear that EditOperation.Create() provides an Action overload that returns the ObjectID, and this could be used to create an association. Remember, however, that all of the edits specified in an EditOperation are not actually run until Execute() is called. Therefore the ObjectIDs are not yet available in the edit operation.

Instead, there are methods on EditOperation for feature creation called CreateEx(). These methods work in the same way as Create() but return a RowToken. The RowToken object represents a row to be created. These RowTokens can be used to create RowHandles, which are in turn passed to association description objects.

The following code snippet shows how to create a pole, a transformer bank, and a structural attachment association between the two in a single edit operation:

// Create an EditOperation
EditOperation editOperation = new EditOperation();
editOperation.Name = "Create pole; create transformer bank; attach transformer bank to pole";

// Create the transformer bank
RowToken transformerBankToken = editOperation.CreateEx(transformerBankLayer, transformerBankAttributes);

// Create a pole
RowToken poleToken = editOperation.CreateEx(poleLayer, poleAttributes);

// Create a structural attachment association between the pole and the transformer bank
RowHandle poleHandle = new RowHandle(poleToken);
RowHandle transformerBankHandle = new RowHandle(transformerBankToken);

AssociationDescription poleAttachment = new AssociationDescription(AssociationType.Attachment, poleHandle, transformerBankHandle);

editOperation.Create(poleAttachment);

// Execute the EditOperation
editOperation.Execute();
⚠️ **GitHub.com Fallback** ⚠️