ProConcepts Utility Network

Uma Harano edited this page Jun 26, 2018 · 3 revisions

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# and Visual Basic
Subject:       Utility Network
Contributor:   ArcGIS Pro SDK Team <arcgisprosdk@esri.com>
Organization:  Esri, http://www.esri.com
Date:          06/20/2018
ArcGIS Pro:    2.2
Visual Studio: 2015, 2017  

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 and not client-server. The utility network API is likewise based on a services-only architecture and 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 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, and network diagram items are included in ArcGIS.Core.Data.UtilityNetwork.NetworkDiagrams.

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 InvocationTarget as a parameter, which is an enum with two values: SynchronousService and AsynchronousService. 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.

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. These include the dirty areas table, the errors table, and the subnetworks table. These tables are available to the application developer using the GetSystemTable() method on the UtilityNetwork class.

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:

  • 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 and Sink.
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
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.
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.
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, and Association.

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).
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.

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 5 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).

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 ValidateNetworkTopologyInEditOperation() extension method on the UtilityNetwork class. If successful, this routine refreshes map views and creates an entry on the Pro operation stack.

There are a number of different overloads of this method:

Method Behavior
ValidateNetworkTopologyInEditOperation() Validates the entire topology. The asynchronous version of the service is used.
ValidateNetworkTopologyInEditOperation(Geometry extent) Validates the topology within the given extent. Intended for use with small areas, the synchronous version of the service is used.
ValidateNetworkTopologyInEditOperation(Geometry extent, InvocationTarget invocationTarget) 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.

See the section Synchronous and Asynchronous Services for more information on the different invocation patterns.

If writing 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().

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.

Association Type Creation Deletion
Connectivity AddConnectivityAssociation() DeleteConnectivityAssociation()
Containment AddContainmentAssociation() DeleteContainmentAssociation()
Structural Attachment AddStructuralAttachmentAssociation() DeleteStructuralAttachmentAssociation()

In the case of AddContainmentAssociation(), a method overload allows the content to be specified as visible outside of container mode.

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.

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.

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 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). This is accomplished through the Export Subnetwork geoprocessing tool, passing in the ExportAcknowledged flag.

Future versions of the utility network API may include a Subnetwork.Export() method.

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);

Finally, external systems must be updated, and this is accomplished using the Export Subnetwork geoprocessing tool, passing in the ExportAcknowledged flag. 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.

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.
Invalid The subnetwork has been completely deleted (no database rows exist), but the C# object remains. Once a subnetwork is in this state, most properties and methods will throw an exception.
All This ORs all the enum values together to provide an easy way to fetch all 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.

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.

Retrieving subnetworks

Existing subnetworks can be retrieved by calling the GetSubnetworks() method on the 1SubnetworkManager1 object. The parameter 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.

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.

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

IReadOnlyList<Subnetwork> subnetworks = subnetworkManager.GetSubnetworks(tier, SubnetworkStates.Dirty | SubnetworkStates.DirtyAndDeleted);
foreach (Subnetwork subnetwork in subnetworks)
{
  subnetwork.Update();
}
mapView.Redraw(true);

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>();
}

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, result types, and a trace configuration.

Starting locations are Elements that specify the starting point of a trace, and barriers (also Elements) prevent a trace from continuing. 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;

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
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.
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).
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 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 either type of output filter is satisfied, the row is 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 };
 }

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 two subclasses of Result:

  • ElementResult contains a list of Element objects that meet the tracing criteria.
  • 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.

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;

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

TraceConfiguration is a property 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
IncludeBarriersWithResults
IncludeContainers
IncludeContent
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 defined by Tier.TraceConfiguration. 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
IncludeBarriersWithResults
IncludeContainers
IncludeContent
IncludeStructures
OutputAssetTypes
OutputCondition
Propagators
ShortestPathNetworkAttribute
SourceTier
TargetTier
Traversability
ValidateConsistency

Network diagrams

The network diagrams API provides access to the network diagrams framework. It provides classes that allow you to do the following:

  • Retrieve existing diagram templates.
  • Create, store, and delete network diagrams.
  • Retrieve stored network diagrams that cover a given extent or contain particular utility network elements.
  • Update, overwrite, append, or extend network diagrams.
  • Apply predefined layout algorithms on the entire content or parts of a network diagram.
  • Retrieve the utility network network features related to a set of diagram features.
  • Code custom layouts on a network diagram.

The network diagram does not provide functionality to create or configure diagram templates. These tasks can be accomplished using the geoprocessing API.

Diagram manager

The DiagramManager class serves as the core class in the network diagrams API. It can be obtained using the GetDiagramManager() method on the UtilityNetwork class.

The diagram manager can be used for the following:

Diagram template

A diagram template contains the configuration properties defining the content (diagram rule and layout definitions) and presentation (diagram layer definitions) of a type of network diagram. If the name of a template is known, it can be obtained from the DiagramManager class using the GetDiagramTemplate() method. The GetDiagramTemplates() method on the same class returns a list of all the diagram templates in the system.

NetworkDiagram class

This class represents a diagram generated from a portion of the utility network.

New network diagrams are created using the CreateNetworkDiagram() factory method on the DiagramManager class. This routine creates a temporary network diagram from a diagram template and a set of utility network feature GlobalIDs.

Existing network diagrams can be retrieved from either the diagram manager or the diagram template used to create them. Network diagrams can be fetched by name, from an envelope, or from a set of utility network feature GlobalIDs using the GetNetworkDiagram() and GetNetworkDiagrams() overloads on both the DiagramManager and DiagramTemplate classes.

Network diagram basics

The NetworkDiagram class includes a number of properties and methods that provide information about the diagram. Most can be explained from the class reference.

GetConsistencyState() returns the consistency state of the diagram.

Consistency State Description
DiagramIsConsistent The diagram and the features that comprise it are both consistent and validated.
DiagramNotConsistentWithTopology The network topology has been validated for the diagram features, but the diagram has not yet been updated. Call NetworkDiagram.Update() to update the diagram.
DiagramHasDirtyFeatures The network topology has not been validated for the diagram features. Call UtilityNetwork.ValidateNetworkTopology().

Additional information about a network diagram can be obtained by calling the GetDiagramInfo() method. The following code shows how to examine one of the NetworkDiagramInfo properties to return a list of inconsistent nonsystem network diagrams:

public List<NetworkDiagram> GetInconsistentDiagrams(UtilityNetwork utilityNetwork)
{
  // Get the DiagramManager from the utility network

  using (DiagramManager diagramManager = utilityNetwork.GetDiagramManager())
  {
    List<NetworkDiagram> myList = new List<NetworkDiagram>();

    // Loop through the network diagrams in the diagram manager

    foreach (NetworkDiagram diagram in diagramManager.GetNetworkDiagrams())
    {
      NetworkDiagramInfo diagramInfo = diagram.GetDiagramInfo();

      // If the diagram is not a system diagram and is in an inconsistent state, add it to our list

      if (!diagramInfo.IsSystem && diagram.GetConsistencyState() != NetworkDiagramConsistencyState.DiagramIsConsistent)
      {
        myList.Add(diagram);
      }
      else
      {
        diagram.Dispose(); // If we are not returning it we need to Dispose it
      }
    }

    return myList;
  }
}

Diagram elements

A network diagram consists of a set of diagram elements that are either junctions, edges, or containers. The elements are represented by the DiagramElement class hierarchy.

The AssociatedGlobalID and AssociatedSourceID properties can be used to retrieve the corresponding utility network row. AssociatedSourceID corresponds to the ID property of the NetworkSource class.

The ID property returns the internal diagram ID of the element. The FromID and ToID properties of an edge diagram element refer to the ID of the two DiagramJunctionElements on either side. Likewise, the ContainerID property refers to the ID of the DiagramElement that is the container.

Diagram elements are returned by diagram element queries.

Diagram aggregations

Diagram aggregations are created when a utility network feature is reduced or collapsed in a network diagram. The utility network feature does not, therefore, correspond to a single diagram element. The GetAggregations() method on the NetworkDiagram object returns a list of DiagramAggregation objects. These provide details on all the diagram elements that are aggregated in the diagram, this is, reduced or collapsed.

The properties of this object are as follows:

Property Description
AggregatedBy The diagram element ID for the diagram element under which it has been reduced or collapsed.
AggregationType The type of network diagram aggregation (described in detail below).
AssociatedGlobalID, AssociatedSourceID These two properties can be used to retrieve the corresponding utility network feature. AssociatedSourceID refers to the ID property of a Network Source object.

The aggregation types are described as follows:

  • JunctionAggregation—For utility network features aggregated under a diagram junction element. This diagram junction may correspond to the following:
    • A utility network container point or polygon represented as a point in the diagram. This diagram point is related to a set of utility network content features that have been collapsed in the network diagram.
    • A utility network junction point represented as a point in the diagram. This diagram point is related to a set of utility network features that have been reduced in the network diagram.
  • EdgeAggregation—For utility network features aggregated under a diagram edge element. This diagram edge may correspond to the following:
    • A utility network container line represented as an edge in the diagram. This diagram edge is related to a set of utility network content features that have been collapsed in the network diagram.
    • A reduction edge diagram. This diagram edge is related to a set of utility network features that have been reduced in the network diagram.
  • ContainerAggregation—For utility network features reduced under a diagram polygon. This diagram polygon may correspond to the following:
    • A utility network container point or polygon represented as a polygon in the diagram with all or part of its related content features inside the diagram polygon.
  • NoneAggregation—For utility network features aggregated in the diagram without a corresponding diagram element.
    • For example, unconnected system junctions are systematically reduced to nothing when running a Reduce Junction rule that processes unconnected junctions.
    • In the same way, depending on the Reduce Junction rule settings, certain disconnected portions of the utility network may be reduced to nothing in the resulting diagram.

Querying network features

Network diagrams provide two methods to fetch feature information.

First, you can fetch the information from diagram features using the FindDiagramFeatures() method. This method retrieves a list of FindResultItem objects that correspond to all features in the diagram that meet the following conditions:

  • Associated with a set of utility network feature GlobalIDs
  • Represent connectivity associations

Features that are represented in the diagram or are aggregated within the diagram are both included.

The FindResultItem class provides GlobalID, ObjectID, SourceID (corresponding to NetworkSource.ID) and GeometryType properties.

Second, the FindNetworkRows() method provides similar functionality, returning a list of FindResultItem objects that correspond to utility network rows. FindInitialNetworkRows() is a similar method that only returns the network rows initially used as input for the diagram generation.

These two methods can be used to build extensions that communicate information back and forth between diagrams and regular maps. Some examples from the core product are the Apply To Diagrams and Apply To Maps commands on the Utility Network and Network Diagram tab groups on the ribbon.

Querying diagram elements

Diagram elements are returned using the QueryDiagramElements() method on the NetworkDiagram class. There are three overloads for this method.

Retrieving diagram elements by type

The QueryDiagramElements(DiagramElementQueryByElementTypes query) overload takes a DiagramElementQueryByElementTypes object to specify the types of diagram elements to return from a network diagram.

Retrieving diagram elements by type and extent

The QueryDiagramElements(DiagramElementQueryByExtent query) overload takes a DiagramElementQueryByExtent object to specify the extent to use as a constraint to retrieve the diagram elements.

Retrieving diagram elements by object IDs

Finally, the QueryDiagramElements(DiagramElementQueryByObjectIDs query) overload takes a DiagramElementQueryByObjectIDs object to retrieve the diagram elements that correspond to a set of object IDs from diagram features.

Results from diagram element queries

The DiagramElementQueryResult class returns the set of diagram elements that are returned from the query.

The following code example shows how to retrieve junction diagram elements whose extent intersects a map extent.

// Create a DiagramElementQueryByExtent to retrieve diagram element junctions whose extent
// intersects the active map extent

DiagramElementQueryByExtent elementQuery = new DiagramElementQueryByExtent();
elementQuery.ExtentOfInterest = MapView.Active.Extent;
elementQuery.AddContents = false;
elementQuery.QueryDiagramJunctionElement = true;
elementQuery.QueryDiagramEdgeElement = false;
elementQuery.QueryDiagramContainerElement = false;

// Use this DiagramElementQueryByExtent as an argument to the QueryDiagramElements method

DiagramElementQueryResult result = networkDiagram.QueryDiagramElements(elementQuery);

Editing network diagrams

Network diagrams can be updated with the Update() method. This synchronizes a diagram based on the latest network topology; it incorporates any changes that were made to the features since the last update.

Append() is used to add features to an existing network diagram, while Overwrite() completely rebuilds the network diagram from a new set of features. Extend() is used to extend the entire content of the network diagram by one-row level of adjacency. The NetworkDiagramExtendType designates whether the one level of adjacency applies to connectivity, attachment, or containment. Not every diagram can be extended, the NetworkDiagramInfo.CanExtend property can be used to check this.

Store() persists a temporary network diagram in the database, and Delete() removes it from the database.

None of these editing methods can process subnetwork system diagrams. These can only be updated by Subnetwork.Update() or deleted by the subnetwork. In addition, not every diagram template permits storage. The NetworkDiagramInfo.CanStore property can be used to check this.

Diagram flags

Network diagrams also contain a set of flags, which can be retrieved by type using the GetFlags() method. This method returns a set of NetworkDiagramFlag classes.

Likewise, flags can be added with AddFlag() and removed with RemoveFlag(), which removes a single flag, or RemoveFlags(), which removes all of them.

Diagram layouts

There are three ways that can be used to change the layout of a diagram:

  • Apply a predefined diagram layout
  • Create a custom layout
  • Reapply the template layout

These are described in the sections that follow.

Apply a predefine diagram layout

The API provides a class hierarchy of DiagramLayoutParameters classes. The concrete subclasses each correspond to a diagram layout that is available in the ArcGIS Pro user interface.

The properties in these classes are used to specify the desired layout. This parameter is then passed to NetworkDiagram.ApplyLayout() to change the layout of the diagram.

If you only want to change the layout of a subset of the diagram, you can create and use a DiagramElementObjectIDs object and also pass that to NetworkDiagram.ApplyLayout().

The collection of ObjectIDs passed into the DiagramElementObjectIDs can come from a map selection or a DiagramElement query.

Create a custom layout

To create a completely custom layout, the first step is to gather a set of diagram elements with a DiagramElement query. The Shape property of these diagram elements can be modified according to a custom algorithm. Once they are edited, they can be packaged into a NetworkDiagramSubset object and passed into the SaveLayout() method.

The following code example shows how to use a diagram query to fetch a set of diagram elements. The shapes of the elements are changed, and the SaveLayout() method is used to update the diagram. This allows you to write your own layout algorithms.

// Retrieve a diagram
using (NetworkDiagram diagramTest = diagramManager.GetNetworkDiagram(diagramName))
{
  // Create a DiagramElementQueryByElementTypes query object to get the diagram elements we want to work with
  DiagramElementQueryByElementTypes query = new DiagramElementQueryByElementTypes();
  query.QueryDiagramJunctionElement = true;
  query.QueryDiagramEdgeElement = true;
  query.QueryDiagramContainerElement = true;

  // Retrieve those diagram elements
  DiagramElementQueryResult elements = diagramTest.QueryDiagramElements(query);

  // Create a NetworkDiagramSubset object to edit this set of diagram elements
  NetworkDiagramSubset subset = new NetworkDiagramSubset();
  subset.DiagramJunctionElements = elements.DiagramJunctionElements;
  subset.DiagramEdgeElements = elements.DiagramEdgeElements;
  subset.DiagramContainerElements = elements.DiagramContainerElements;

  // Edit the shapes of the diagram elements - left as an exercise for the student
  TranslateDiagramElements(subset);

  // Save the new layout of the diagram elements
  diagramTest.SaveLayout(subset, true);
}
Reapply the template layout

The final way to change a layout is to reapply the template layout with the ApplyTemplateLayouts() method.

Network diagram transaction semantics

The network diagram editing methods provide their own transaction management. Therefore, they cannot be included in another editing transaction (for example, Geodatabase.ApplyEdits()). In addition, because the edits performed by these methods cannot be undone, they cannot be called from within an existing editing session; all existing edits must be saved or reverted prior to calling these methods.

These rules apply to the following methods on the NetworkDiagram class:

  • AddFlag()
  • Append()
  • ApplyLayout()
  • ApplyTemplateLayouts()
  • Delete()
  • ExtendDiagram()
  • Overwrite()
  • RemoveFlag()
  • RemoveFlags()
  • SaveLayout()
  • Store()
  • Update()

The same restrictions also apply to DiagramManager.CreateNetworkDiagram().

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.

Diagram layers

The DiagramLayer class represents a network diagram layer in a network diagram window (a kind of map). This class resides in a mapping rather than a geodatabase namespace, ArcGIS.Desktop.Mapping.

The GetNetworkDiagram() method returns the NetworkDiagram class displayed by this window.

The ConsistencyState property returns the consistency state of the diagram. The possible values are DiagramIsConsistent, DiagramNotConsistentWithTopology, and DiagramHasDirtyFeatures.

The DiagramLayer class inherits from CompositeLayer.

The following code snippet shows how to take a network diagram and open a network diagram window that corresponds to it:

// Create a diagram layer from a NetworkDiagram (myDiagram)
DiagramLayer diagramLayer = await QueuedTask.Run<DiagramLayer>(() =>
{
  // Create the diagram map
  var newMap = MapFactory.Instance.CreateMap(myDiagram.Name, ArcGIS.Core.CIM.MapType.NetworkDiagram, MapViewingMode.Map);
  if (newMap == null)
    return null;

  // Open the diagram map
  var mapPane = ArcGIS.Desktop.Core.ProApp.Panes.CreateMapPaneAsync(newMap, MapViewingMode.Map);
  if (mapPane == null)
    return null;

  //Add the diagram to the map
  return newMap.AddDiagramLayer(myDiagram);
});

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_utilityNetworkLayerSelectedCondition"

This will cause the button to be enabled 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

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 diagrams 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, poleGuid);
RowHandle poleRowHandle = new RowHandle(poleElement, utilityNetwork);

// Create a RowHandle for the transformer bank

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

// Attach the transformer bank to the pole

StructuralAttachmentAssociationDescription structuralAttachmentAssociationDescription = new StructuralAttachmentAssociationDescription(poleRowHandle, transformerBankRowHandle);
editOperation.Create(structuralAttachmentAssociationDescription);
editOperation.Execute();

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);

StructuralAttachmentAssociationDescription poleAttachment = new StructuralAttachmentAssociationDescription(poleHandle, transformerBankHandle);

editOperation.Create(poleAttachment);

// Execute the EditOperation
editOperation.Execute();

Home

Developing with ArcGIS Pro


Framework

    Add-ins

    Configurations

    Customization

    Styling


Content


CoreHost


DataReviewer


Editing


Geodatabase


Geometry

    Relational Operations


Geoprocessing


Layouts


Map Authoring


Map Exploration

    Map Tools


Raster


Sharing


Tasks


Utility Network


Workflow Manager


Reference

    2.0 Migration

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.