ProConcepts Utility Network - Esri/arcgis-pro-sdk GitHub Wiki
The utility network is a comprehensive framework of functionality in ArcGIS for modeling utility systems such as electric, gas, water, storm water, wastewater, and telecommunications. This topic provides an introduction to the utility network API. It details the classes and methods that query and edit the utility network. The utility network API is commonly used in conjunction with the geodatabase and editing.
Language: C#
Subject: Utility Network
Contributor: ArcGIS Pro SDK Team <[email protected]>
Organization: Esri, http://www.esri.com
Date: 05/19/2022
ArcGIS Pro: 3.4
Visual Studio: 2022
- Introduction
- UtilityNetwork class
- Definition and schema
- Element class
- Network topology
- Associations
- Subnetworks
- Tracing
- Pro Integration
-
The utility network API is a Data Manipulation Language (DML)-only API. This means that all schema creation and modification operations such as creating domain networks, adding and deleting rules, and so on, need to use the geoprocessing API.
-
Almost all of the methods in the utility network API should be called on the Main CIM Thread (MCT). The API reference documentation on the methods that need to run on the MCT are specified as such. These method calls should be wrapped inside the QueuedTask.Run call. Failure to do so will result in ConstructedOnWrongThreadException being thrown. See Working with multithreading in ArcGIS Pro to learn more.
-
In standard usage, access to a utility network takes place via services or with file or mobile geodatabases. The utility network API is designed accordingly. Implications of this architecture are described throughout this topic as appropriate.
Note: This topic assumes a basic understanding of the utility network information model. See the online help for more information.
Like the geodatabase API, many objects in the managed utility network API use unmanaged resources (i.e. resources not managed by garbage collection) that must be explicitly released by the application. Unmanaged resources include file locks and database connections, among others. Since the utility network is often a services-based system, many of these resources actually exist on the server.
The preferred pattern to manage these underlying unmanaged resources is by calling Dispose once you are done using them. Dispose frees up the unmanaged resources, releasing any underlying file locks or active database connections (You can also use a “using” construct that will call Dispose for you - there are many examples of “using” in the Pro snippets and samples).
More information on these patterns, along with special cases that you should consider, can be found in the resource management section of the geodatabase ProConcepts doc.
The main items of the utility network are included in the ArcGIS.Core.Data.UtilityNetwork namespace
. Tracing items are included in ArcGIS.Core.Data.UtilityNetwork.Trace
.
There are some cases where the objects in ArcGIS.Core.Data
need to integrate with ArcGIS Pro at a higher level in the architectural stack. In these cases, C# extension methods are provided. To use these extension methods:
- Add a reference to
ArcGIS.Desktop.Extensions
to your solution - Add
using ArcGIS.Core.Data.UtilityNetwork.Extensions;
to the top of your source files
These steps allow these extension methods to appear as if they were regular methods on utility network classes.
These extension methods are intended for use in ArcGIS Pro Add-ins, and cannot be used with CoreHost applications.
Most of the utility network-related REST services are synchronous. These use the Utility Network Server and have low processing overhead. Unfortunately, they can be susceptible to timeout if processing takes too long. To work around this, several of the REST endpoints provide asynchronous versions. Processing is offloaded from the Utility Network Server to the Geoprocessing Server. While this does cause a performance hit, the Geoprocessing Server is designed to handle long-running jobs and is not susceptible to timeouts. With these services, ArcGIS Pro will poll the service until the operation completes.
Some of the C# methods in the utility network API can provide access to the asynchronous versions of these REST endpoints. These take an ServiceSynchronizationType
as a parameter, which is an enum with two values: Synchronous
and Asynchronous
. It's important to note that from a C# developer's perspective, these are synchronous C# methods. Like other methods in the API, they will block the current thread until the operation completes.
When calling these routines on a file or mobile geodatabase, the ServiceSynchronizationType
parameter is ignored.
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.
Utility networks are implemented in the geodatabase as controller datasets. Other controller datasets in ArcGIS include network datasets for transportation networks, and topology for managing coincident features.
The UtilityNetwork
class provides an abstraction of this controller dataset. Methods on this class provide an entry point to the other areas of the utility network API.
As with other datasets in the geodatabase, a UtilityNetwork object can be obtained by calling Geodatabase.OpenDataset()
. UtilityNetwork objects can also be obtained from a table or feature class that belongs to a utility network by using Table.GetControllerDatasets()
. Note that a particular feature class can belong to multiple controller datasets.
The
Geodatabase.OpenDataset()
routine takes the name of a dataset to open. Remember that when using feature services, the name of the dataset in the feature service workspace does not match the name in the client-server workspace. You typically need to get the correct name from the corresponding definition object.
A UtilityNetwork object can be obtained from a table as follows:
public static UtilityNetwork GetUtilityNetworkFromTable(Table table)
{
UtilityNetwork utilityNetwork = null;
if (table.IsControllerDatasetSupported())
{
// Tables can belong to multiple controller datasets, but at most one of them will be a UtilityNetwork
IReadOnlyList<Dataset> controllerDatasets = table.GetControllerDatasets();
foreach (Dataset controllerDataset in controllerDatasets)
{
if (controllerDataset is UtilityNetwork)
{
utilityNetwork = controllerDataset as UtilityNetwork;
}
else
{
controllerDataset.Dispose();
}
}
}
return utilityNetwork;
}
In addition to user-editable feature classes, utility networks also contain several other tables that are available to users. Some examples of these are the dirty areas table, the errors table, and the subnetworks table.
The IsSystemTableSupported()
method on the UtilityNetwork
class can be used to determine if the desired system table exists (these tables can vary depending on the version of the utility network schema). These tables are then available by using the GetSystemTable()
method.
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
andTerminal
classes. - Finally, the rules of a utility network are described by the
Rule
andRuleElement
classes.
The UtilityNetworkDefinition
class follows the same access pattern used by other definition classes in the geodatabase API.
As with other definition classes, there are two options for accessing UtilityNetworkDefinition:
- Open the UtilityNetworkDefinition from a geodatabase—Typically this is used when it is not anticipated that the utility network will be opened.
UtilityNetworkDefinition definition = geodatabase.GetDefinition<UtilityNetworkDefinition>("UtilityNetworkName");
- Open the UtilityNetworkDefinition directly from the UtilityNetwork—This is used when the dataset is already open and the reference is accessible.
UtilityNetworkDefinition definition = utilityNetwork.GetDefinition();
Most of the methods on this class are described in the sections that follow. Additional methods are detailed below:
-
GetAssetGroupField()
returns the field name that stores the asset group of a row. -
GetAssetTypeField()
returns the field name that stores the asset type of a row. -
GetAssociationStatusField()
returns the field name of the association status field. -
GetAvailableCategories()
returns a list of the categories that have been set on the utility network using the Add Category geoprocessing tool. Categories are strings that serve as tags to identify asset types in a data model-independent fashion. -
GetSchemaVersion()
returns a string representation of the version of the utility network schema. -
GetServiceTerritoryEnvelope()
returns the extent of the utility network (that is, the extent of the service territory polygon, plus 10 percent).
A domain network represents a type of utility service that an organization serves. Domain networks can represent different levels of the same utility resource such as distribution and transmission levels for gas, water, or electricity. Domain networks can also represent multiple types of commodities such as natural gas and electricity. In addition, every utility network contains a domain network for the structure network.
Calling UtilityNetworkDefinition.GetDomainNetworks
returns a read-only list of DomainNetwork objects. Calling UtilityNetworkDefinition.GetDomainNetwork
returns the DomainNetwork object with a specific name.
Domain networks can return lists of network sources (DomainNetwork.NetworkSources
) and tiers (DomainNetwork.Tiers
). Additional properties on the DomainNetwork
class are as follows:
Property | Description |
---|---|
IsStructureNetwork | Returns whether this domain network represents the structure network. |
SubnetworkControllerCategory | Returns the type of subnetwork controllers supported in this domain network. Valid values in the SubnetworkControllerCategory enum are Source , Sink , and None (for structure networks). |
TierDefinition | Returns the type of subnetworks supported in this domain network. Valid values in the TierDefinition enum are Hierarchical (typically used with pressure networks) and Partitioned (typically used with electrical networks). |
Tiers model the hierarchy of how the network delivers a resource such as natural gas, electricity, or water. A tier typically represents a pressure or voltage level. For example, an electric distribution system can be subdivided into subtransmission, medium-voltage, and low-voltage levels and some types of analysis should be performed only within one of these levels. A tier can also represent parts of the network that can be isolated from one another such as a valve isolation zone in a pressure-based system. Tiers are useful because they allow you to constrain the valid row types for each tier, and they can also define the extent of network tracing analysis.
Tier objects can be obtained with the DomainNetwork.Tiers
property, which returns a list of all of the tiers on the DomainNework, or with the DomainNetwork.GetTier
method, which returns a single tier by name.
The following code demonstrates how to find a "Medium Voltage" tier from an "Electric Distribution" domain network:
using (UtilityNetworkDefinition utilityNetworkDefinition = utilityNetwork.GetDefinition())
{
DomainNetwork domainNetwork = utilityNetworkDefinition.GetDomainNetwork(domainNetworkName);
Tier tier = domainNetwork.GetTier(tierName);
}
When tiers are created using the Add Tier geoprocessing tool, several collections of asset types must be provided. These asset types can be retrieved by the following sets of properties:
Property | Description |
---|---|
ValidDevices | The device asset types that can be included within subnetworks in this tier |
ValidEdgeObjects | The edge object asset types that can be included within subnetworks in this tier |
ValidJunctionObjects | The junction object asset types that can be included within subnetworks in this tier |
ValidJunctionObjectSubnetworkControllers | The junction object asset types that can serve as subnetwork controllers within this tier |
ValidJunctions | The junction asset types that can be included within subnetworks in this tier |
ValidLines | The line asset types that can be included within subnetworks in this tier |
ValidSubnetworkControllers | The asset types that can serve as subnetwork controllers within this tier |
ValidSubnetworkLines | The asset types that are used to build features in the SubnetLine feature class when updating the subnetwork |
Other subnetwork properties and methods on the Tier
class are as follows:
Property or Method | Description |
---|---|
DomainNetwork | Returns the DomainNetwork that contains this tier. |
GetDiagramTemplateNames() | Returns the names of the subnetwork diagram templates for this tier. |
GetEditModeForSubnetwork | Whether events are fired when updating subnetworks for this tier for a particular version type. Events control whether attribute rules fire. |
HasUpdateSubnetworkPolicy | Whether containers and structures are updated when updating subnetworks. |
IsDisjointSubnetworkSupported | A disjoint subnetwork contains at least one subnetwork controller that cannot be traversed from its other subnetwork controllers. If this property is false, and the subnetwork is disjointed, calling Subnetwork.Update() will result in an error. |
SubnetworkFieldName | The name of the field that stores the subnetwork name. |
TopologyType | The type of subnetworks that are supported. Valid values are Radial , and Mesh . |
TraceConfiguration | The trace configuration that was set by the Set Subnetwork Definition geoprocessing tool. |
Tier groups 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.
Utility networks are created from different sources of information. The most obvious are the structure feature classes (StructureLine, StructurePoint, and StructureBoundary) and the feature classes that are included with each domain network (Device, Line, Junction, Assembly, and SubnetLine). Other sources of information are the set of associations that have been created, and the system junctions that are automatically generated where needed. These sources of information are collectively known as network sources and are represented by the NetworkSource
class.
Network source objects can be obtained with the GetNetworkSource()
and GetNetworkSources()
methods on the UtilityNetworkDefinition class, as well as from the NetworkSources
property on the DomainNetwork class. GetNetworkSource()
is used to return a single NetworkSource by name, while GetNetworkSources()
returns a list of all the network sources on the UtilityNetworkDefinition or DomainNetwork.
The UsageType
property describes how the table is used in the network. Possible values are Device
, Junction
, Line
, Assembly
, SubnetLine
, StructureJunction
, StructureLine
, StructureBoundary
, SystemJunction
, Association
, JunctionObject
, EdgeObject
, StructureJunctionObject
, and StructureEdgeObject
.
Finally, the GetAssetGroup
and GetAssetGroups
methods return the asset groups for this particular network source. GetAssetGroup
returns a single asset group by name, while GetAssetGroups
returns a list of asset groups.
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 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.
The AssetType
class represents the secondary classification of asset group types. The ASSETTYPE field is implemented as a set of coded-value domains for each asset group.
The following are examples of asset types:
- Asset types for a transformer asset group in an electric distribution domain network could be StepTransformer, PowerTransformer, and DistributionTransformer.
- Asset types for a line feature class in a water distribution domain network could be PVCPipe, ClayPipe, and CastIronPipe.
The AssetType
class supports a number of properties and methods that describe the characteristics of this type.
Property or Method | Description |
---|---|
IsLinearConnectivityPolicySupported() | Returns whether or not the GetLinearConnectivityPolicy() method is applicable. |
GetLinearConnectivityPolicy() | Returns whether connectivity for this asset type can be established at any vertex (AnyVertex ) or only at end points (EndVertex ). |
IsContainerSplitPolicySupported() | Returns whether or not the GetContainerSplitPolicy() method is applicable. |
GetContainerSplitPolicy() | Returns the behavior that occurs when containers with this asset type are split. If set to SplitContent , this means that content features are also split; DoNotSplitContent indicates that content features are not split. |
AssociationDeletionSemantics | Returns the deletion type for this asset type (Cascade , None , or Restricted ). |
ContainerViewScale | Returns the default scale of any containers created from this asset type. If the asset type is not a container, this routine returns 0.0. |
AssociationRoleType | Returns whether the asset type can be a Container , Structure , or None (neither). |
GetCategoryList() | Returns the supported categories. Categories are strings that serve as tags to identify asset types in a data model-independent fashion. |
IsTerminalConfigurationSupported() | Returns whether or not the GetTerminalConfiguration() method is applicable. |
GetTerminalConfiguration() | Returns the terminal configuration for this asset type. |
Network attributes are values that are stored for each row in the underlying network topology. The values are read from database attributes and are stored in the topology when it is validated. They can then be accessed directly from the topology without requiring an additional fetch of the corresponding database row, resulting in significant performance benefits when used with tracing. The same network attribute can be assigned to different tables. Some example network attributes are life cycle status, phase, and valve position.
Network attribute objects can be obtained with the GetNetworkAttribute
and GetNetworkAttributes
methods on the UtilityNetworkDefinition
class. GetNetworkAttribute
returns a single network attribute by name, while GetNetworkAttributes
returns a list of all the network attributes. They provide a number of properties that describe the network attribute. The Assignments
property returns a list of NetworkAttributeAssignment objects. The NetworkAttributeAssignment
class provides a mapping between network attributes and database fields. The Type
property specifies the data type of the network attribute, NetworkAttributeDataType
, which has Date
, Double
, Long
, and Short
types.
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.
Terminal configurations can contain one or more configuration paths, which specify a flow path through terminals. Configuration paths can only be specified for devices with fewer than five terminals. Configuration paths can specify whether flow through them is one-way or bidirectional.
Each configuration path contains a set of TerminalPath
objects, which designate a flow path between a pair of terminals. If the Description
field is set to the hard-coded value "All", the TerminalPaths parameter will be empty, but there is an implied set of flow paths linking each terminal to every other terminal in the device.
Consider the example of a bypass switch, which is used in conjunction with an electrical device such as a voltage regulator. In normal operation, the blades inside of a bypass switch are configured to allow power to flow through the voltage regulator (represented below as a circle with an 'x').
When the voltage regulator is taken out of service, the disconnect blades are opened and the bypass blade is closed, isolating the voltage regulator from electrical current.
Drawn graphically, the terminal configuration looks like the following, where the green circles are the terminals on the bypass switch and the gray circles represent the voltage regulator.
Using pseudo-code, the TerminalConfiguration
object looks like the following:
Name = “Bypass Switch”
Terminals = { terminalA, terminalB, terminalC, terminalD }
ValidConfigurationPaths = { NormalOperation, Bypass }
DefaultConfigationPath = NormalOperation
The NormalOperation configuration path looks like the following:
Name = NormalOperation
TerminalPaths =
{ terminalA, terminalC },
{ terminalD, terminalB }
When in this configuration, terminalA is connected to terminalC and terminalB is connected to terminalD.
For Bypass, the configuration path looks like the following:
Name = ”Bypass”
TerminalPaths =
{ terminalA, terminalB }
When in this configuration, terminalA is connected to terminalB.
The ConfigurationPath
value for a given Device feature is stored in the TerminalConfiguration
field on that feature, and is edited using normal geodatabase editing routines.
Rules 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. |
The Element
class represents a row in a utility network, plus a terminal (if applicable). These value objects are used across the utility network SDK. Places where Element
is used are as follows:
- Elements are used to create and delete associations.
- Elements specify starting points and barriers for use with tracing.
- Elements are returned as results from traces.
Elements are created using the CreateElement()
factory methods on the UtilityNetwork
class.
If an Element
object represents an edge and is being used as input for a trace, the PercentAlongEdge
property can be used to specify the exact starting point along the edge (0.5 represents a starting point 50% along the edge, starting at the to junction).
When creating a utility network feature, you should always specify which subtype to use when creating the row buffer. This will ensure the row is initialized with all the expected default values for that subtype. Failure to do so may result in unexpected behavior with attribute rules, contingent values, or other configurations that expect default values for specific subtypes.
using(RowBuffer lineRowBuffer = table.CreateRowBuffer(mediumVoltageSubtype))
{
lineRowBuffer["Name"] = "Overhead Line";
using(Row lineRow = table.CreateRow(lineRowBuffer))
{
Element lineElement = utilityNetwork.CreateElement(lineRow);
}
}
The ObjectID
property is only set if the Element
is created from a Row
or is returned from a trace. Otherwise the property returns -1. Likewise, calling the CreateElement
overrides that do not specify a terminal will create an Element
object without a terminal (no default is set).
The
FeatureElement
represents a row inside a utility network returned from a trace operation with theFeatures
result type. It is not only comprised ofElement
properties but also includes network and field attributes.
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:
- Obtain a
NetworkSource
from theElement.NetworkSource
property. - Obtain a
Table
usingUtilityNetwork.GetTable()
, passing in theNetworkSource
. - Fetch the
Row
from theTable
using a standardQueryFilter
and theElement.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;
}
}
}
The network topology stores connectivity, containment, and attachment information used by the utility network to facilitate fast network traversal and analytical operations. Network topology is constructed from geometric coincidence of features and associations in combination with a powerful rules engine.
Information about the state of the network topology can be returned by calling the GetState()
method on the UtilityNetwork
class.
Topology is updated and validated with the ValidateNetworkTopology
method or ValidateNetworkTopologyInEditOperation
extension method on the UtilityNetwork
class.
For a CoreHost application, use the
ValidateNetworkTopology
base method on theUtilityNetwork
class. This routine provides its own transaction management; therefore, it cannot be issued inside an edit operation created byGeodatabase.ApplyEdits()
.
Use the extension method for the Pro SDK add-in. If validation succeeds, it refreshes map views and creates an entry on the Pro operation stack.
The ValidationResult
class describes the result of the validate network topology operation. It contains a set of discovered subnetworks that is marked as dirty during ValidateNetworkTopology()
operation.
There are a number of different overloads of this method:
Base Method | Extension | Behavior |
---|---|---|
ValidateNetworkTopology() |
ValidateNetworkTopologyInEditOperation(UtilityNetwork utilityNetwork) |
Validates the entire topology. The asynchronous version of the service is used. |
ValidateNetworkTopology(Geometry) |
ValidateNetworkTopologyInEditOperation(UtilityNetwork utilityNetwork, Geometry extent) |
Validates the topology within the given extent. Intended for use with small areas, the synchronous version of the service is used. |
ValidateNetworkTopology(Geometry, ServiceSynchronizationType) |
ValidateNetworkTopologyInEditOperation(UtilityNetwork utilityNetwork, Geometry extent, ServiceSynchronizationType serviceSynchronizationType ) |
Providing complete control for the application developer, this version validates the topology within the given extent (pass in null for the entire extent) using the endpoint version specified. |
ValidateNetworkTopology(Geometry, ServiceSynchronizationType, ValidationType) |
N/A | Validates the utility network topology within the provided extent according to the provided ValidationType |
See the section Synchronous and Asynchronous Services for more information on the different service synchronization types. If using a file or mobile geodatabase, the ServiceSynchronizationType
is ignored.
Notably, fine-grained access to the topology network is not provided. For instance, you cannot traverse through a network one element at a time. Each operation would result in a query to the service, and performance and scalability would prove unacceptable. Instead, the utility network provides a powerful tracing framework that removes the need for this kind of traversal.
A future version of the utility network SDK may provide limited query capability against the network topology. Callers may be able to determine what rows are directly connected, attached, or contained to or from an input row. This method may have applicability for some special case workflows and tools. It is not intended to be used repeatedly to traverse through a network and will perform poorly if used in this way.
The UtilityNetwork
class contains methods that query and edit associations. For editing, the UtilityNetwork
class provides methods to create and delete associations. These methods should be wrapped in a transaction like other low-level editing methods.
Associations are editing by using the AddAssociation()
and DeleteAssociation()
methods on the UtilityNetwork
class. These methods take an Association
object as a parameter.
The meaning of the FromElement
and ToElement
properties is described in the table below.
Association Type | FromElement semantics | ToElement semantics |
---|---|---|
Containment | Represents the container | Represents the content |
Structural Attachment | Represents the structure | Represents the attached item |
Non-spatial Connectivity | Represents the junction or non-spatial junction object | Represents the non-spatial edge object |
The IsContainmentVisible
property is only used with containment associations, and the the PercentAlong
property is only used with junction-edge object midspan connectivity associations.
Although ideal for stand-alone applications, using these low-level methods are not recommended in an ArcGIS Pro Add-in. The map is not refreshed by these methods, and the undo/redo stack is not modified. See the Editor integration section for higher-level editing methods designed to integrate with the Pro editor.
The GetAssociations()
method on the UtilityNetwork
class allows associations to be returned, given a particular element.
If you wish to retrieve a set of geometries that represent the associations within a given map extent, the GetAssociationFeatures()
method on the UtilityNetwork
class can be used. This routine only returns geometries from connectivity associations and structural attachment associations.
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.
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.
Certain data models make extensive use of non-spatial objects and associations. A telecommunications splice junction, for example, can contain hundreds or thousands of internal junctions, edges, and associations. In these cases, querying associations one at a time can get prohibitively expensive. The TraverseAssociations()
method on the UtilityNetwork
class is provided to handle these use cases. The method can work with multiple input elements, and can traverse up and down through a graph of associations to return multiple objects. Going back to the telecommunications splice junction example, this routine can start with the splice feature and traverse through all of the containment associations to return the hundreds of objects that are contained within the splice- in a single call.
The TraverseAssoctions()
method in the UtilityNetwork
class provides the information about associations and the field values of elements involved in a traverse associations operation. It takes a set of starting elements and TraverseAssociationsDescription
object as parameters to initiate a traverse associations operation.
The TraverseAssociationsDescription
describes a direction (ascending or descending) and the depth for a traverse associations operation. The depth value determines the maximum number of hops through the association graph to traverse in a specified direction. By default, the depth is set to maximum for full traversal.
// Upward traversal with maximum depth
TraverseAssociationsDescription traverseAssociationsDescription = new TraverseAssociationsDescription(TraversalDirection.Ascending)
// Downward traversal with maximum depth level of 3
TraverseAssociationsDescription traverseAssociationsDescription = new TraverseAssociationsDescription(TraversalDirection.Descending, 3)
Descending in this context includes going from a container to content, going from a structure to a structural attachment, or from the From side of a connectivity association to the To side. Ascending can be used to traverse from content to containers, structural attachments to structures, or from the To side of a connectivity association to the From side. In a junction-edge association, descending is defined as traversing from the junction to the edge.
The TraverseAssociationsDescription
has an AdditionalFields
property that represents a list of additional field names whose values are returned in the result as field name-value pairs of elements. This allows additional data about the objects to be returned without the need for additional queries.
// List of fields whose values will be returned as name-values pairs during association traversal operation
List<string> additionalFieldsToFetch = new List<string> {“OID", "DeviceName", "AssetType", "AssetName", "AssetGroup”}
TraverseAssociationsDescription traverseAssociationsDescription = new TraverseAssociationsDescription(TraversalDirection.Ascending, 2)
{
AdditionalFields = additionalFieldsToFetch
};
The TraverseAssociationsResult
class represents a collection of associations and the mapping between involved elements and their field name-values.
// Get traverse associations result from the staring element up the depth defined in traverseAssociationsDescription
TraverseAssociationsResult traverseAssociationsResult = utilityNetwork.TraverseAssociations(startElements, traverseAssociationsDescription);
// List of associations participated in traversal
IReadOnlyList<Association> associations = traverseAssociationsResult.Associations;
// KeyValue mapping between involved elements and their field name-values
IReadOnlyDictionary<Element, IReadOnlyList<FieldValue>> associationElementValuePairs = traverseAssociationsResult.AdditionalFieldValues;
The FieldValue
has FieldName
, FieldType
, and Value
properties to represent an element’s field name-value pair involved in an association.
Association traversal requires ArcGIS Enterprise 9.0 or higher, and ArcGIS Pro 2.9 or higher.
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.
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
andDisableControllerInEditOperation
, two extension methods on theSubnetworkManager
class. These routines will refresh map views and create operations on the Pro operation stack. If writing a CoreHost application, the base routinesEnableController
andDisableController
should be used instead.
A subnetwork is created by enabling a device as a subnetwork controller. This is accomplished through the EnableControllerInEditOperation()
extension method on the SubnetworkManager
class. This method returns a Subnetwork object with a state of Dirty. EnableControllerInEditOperation
takes an Element as input. The Element must specify a terminal. For source-based domain networks, the terminal must not be an upstream terminal (as determined by Terminal.IsUpstreamTerminal()
). For sink-based domain networks, the terminal must be an upstream terminal.
The following code sample shows how to create a simple radial subnetwork. Note that you need to give both the controller and the subnetwork a name ("R1"). Most utilities will use the same name for both.
Subnetwork R1 = subnetworkManager.EnableControllerinEditOperation(mediumVoltageTier, elementR1, "R1", "R1", "my description", "my notes");
Updating the subnetwork with the Subnetwork.Update()
method changes the state of the subnetwork to Clean. It also creates a corresponding SubnetLine feature (assuming that the subnetwork contains features with one of the asset types defined in the tier definition) and updates subnetwork IDs and other propagated attributes on each network row.
R1.Update();
mapView.Redraw(true);
Editing rows in a subnetwork has no impact on the subnetwork state. However, once the topology is validated, the appropriate subnetworks are moved to the Dirty
state. Again, the Subnetwork.Update()
method is used to return the subnetwork to the Clean
state.
At some point, a subnetwork will need to be deleted. This is accomplished through a multistep process that starts by calling the DisableControllerInEditOperation()
extension method on the SubnetworkManager
class. This moves the subnetwork to the DirtyAndDeleted
state and marks the subnetwork controller as deleted.
subnetworkManager.DisableControllerInEditOperation(elementR1);
At this point, the subnetwork is deleted, but all of the rows that have been labeled with the subnetwork ID need to be updated. Once again, Subnetwork.Update()
is called. This moves the subnetwork to the CleanAndDeleted
state.
subnetworkR1.Update();
mapView.Redraw(true);
At certain points in a utility's business processes, they will want to export their subnetworks to a third-party system such as an outage management system (OMS) or a distribution management system (DMS). The Subnetwork.Export
method is used to export a subnetwork. The export operation generates a JSON file based on the options described in the SubnetworkExportOptions
class, and saves the resulting JSON at the provided path URI.
Finally, external systems must be updated, and this is accomplished by setting SetAcknowledged
to true
in the SubnetworkExportOptions
. This physically deletes the subnetwork rows from the database and leaves the subnetwork in a Deleted
state. At this point, calling further methods and properties on the Subnetwork object will throw an exception.
Set
SetAcknowledged
totrue
during a subnetwork export to delete the record of any subnetwork controllers from the subnetworks table that have been removed as a subnetwork controller. TheSetAcknowledged
is not necessary if there are no controllers that need to be deleted from the subnetworks table.
IReadOnlyList<NetworkAttribute> networkAttributes = utilityNetworkDefinition.GetNetworkAttributes();
IReadOnlyList<NetworkSource> networkSources = utilityNetworkDefinition.GetNetworkSources();
NetworkSource electricDevice = networkSources.First(f => f.Name.Contains("ElectricDevice"));
// Export options
SubnetworkExportOptions subnetworkExportOptions = new SubnetworkExportOptions()
{
SetAcknowledged = false,
IncludeDomainDescriptions = true,
IncludeGeometry = true,
ServiceSynchronizationType = ServiceSynchronizationType.Asynchronous,
SubnetworkExportResultTypes = new List<SubnetworkExportResultType>()
{
SubnetworkExportResultType.Connectivity,
SubnetworkExportResultType.Features
},
ResultNetworkAttributes = new List<NetworkAttribute>(networkAttributes),
ResultFieldsByNetworkSourceID = new Dictionary<int, List<string>>()
{ { electricDevice.ID, new List<string>() { "AssetID" } } }
};
// Execute export
subnetwork.Export(exportResultJsonPath, subnetworkExportOptions);
The following diagram shows the different subnetwork states and the operations used to move between states:
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.
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);
Value | Description |
---|---|
Dirty | Changes have been made to rows in the subnetwork. |
Clean |
Update() has been run subsequent to any edits made to the subnetwork. |
CleanAndAcknowledged | Export with the SetAcknowledged flag has been run after Update() and before additional edits. |
DirtyAndDeleted | All subnetwork controllers have been dropped from the subnetwork, but Update() has not yet been run. |
CleanAndDeleted | All subnetwork controllers have been dropped from the subnetwork, Update() has been run, but Export() has not been run with the SetAcknowledged flag. Once a subnetwork is in this state, running Export() with the SetAcknowledged flag will delete the subnetwork. |
InvalidSubnetworkObject | The subnetwork has been completely deleted (no database rows exist), but the subnetwork object remains. Once a subnetwork is in this state, most properties and methods will throw an exception. |
InvalidWithErrors | The subnetwork has been updated, and an error condition was discovered. Once the subnetwork is in this state, it cannot be updated until edits have been made to correct the error condition and validation has been run successfully. |
All | A combination of the values in this list; provides an easy way to fetch all of the subnetworks in a tier. |
The SubnetworkManager.EnableControllerInEditOperation()
and SubnetworkManager.DisableControllerInEditOperation()
extension methods perform their own transaction management. Because of this, they cannot be called from within another edit operation. In a CoreHost application the base EnableController()
and DisableController()
methods also perform their own transaction management. If called inside Getodatabase.ApplyEdits()
an exception will be thrown.
The Subnetwork.Update()
method has the same restrictions, and one more. Because the edits produced by this method cannot be undone, it cannot be called from within an edit transaction. All edits must be saved or reverted prior to calling this method.
Similar transaction restriction applies to the subnetwork export. When calling Subnetwork.Export
with SubnetworkOptions.SetAcknowledged
as True
inside Geodatabase.ApplyEdits()
, an exception will be thrown.
Enabling and disabling controllers create dirty areas; updating a subnetwork does not.
As shown in the examples above, after updating a subnetwork, ArcGIS.Desktop.Mapping.MapView.Redraw(true)
should be called to refresh the display cache and redraw the map.
In addition to updating subnetworks individually with Subnetwork.Update()
, the API also allows all dirty subnetworks to be updated at once using the SubnetworkManager.UpdateAllSubnetworks()
method. This routine takes a tier and a boolean that specifies whether processing should continue on subsequent subnetworks if a failure is encountered.
The following code example shows how to update all of the dirty subnetworks in a tier:
using (SubnetworkManager subnetworkManager = utilityNetwork.GetSubnetworkManager())
{
subnetworkManager.UpdateAllSubnetworks(tier, true);
mapView.Redraw(true);
}
Existing subnetworks can be retrieved by calling the GetSubnetworks()
method on the SubnetworkManager
object. The method takes a tier parameter, as well as a SubnetworkStates
value, which can include multiple values or'd together. This method returns a list of subnetwork objects. Alternatively, the GetSubnetwork()
method can be called. This method takes a string, which represents a subnetwork name.
In addition to a number of properties, the Subnetwork
class also contains the Update()
method described above. Other methods include GetControllers()
, which returns a list of subnetwork controller objects, and GetLineFeature()
, which returns the SubnetLine feature that corresponds to this subnetwork.
Tracing 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?
Many different classes work together to define and execute a trace:
- Different kinds of traces are implemented with Tracer objects.
- Starting Points, Barriers, Result Types, and a Trace Configuration are inputs to the Tracer object
- The Tracer object generates a set of Result objects as output.
Tracers define the tracing algorithm to be used. Tracer
is an abstract base class with distinct subclasses implementing specific tracing algorithms.
Tracer objects are created using TraceManager
. The TraceManager
class is a central hub to the tracing portions of the API. TraceManager objects are obtained through a call to UtilityNetwork.GetTraceManager()
.
The GetTracer<T>()
method on the TraceManager
class returns a tracer of a specific type as shown in the following code:
using (TraceManager traceManager = utilityNetwork.GetTraceManager())
{
DownstreamTracer downstreamTracer = traceManager.GetTracer<DownstreamTracer>();
}
Likewise, the GetTracer(NamedTraceConfiguration)
method on the TraceManager
class returns a tracer based on the NamedTraceConfiguration as shown below.
using (TraceManager traceManager = utilityNetwork.GetTraceManager())
{
NamedTraceConfigurationQuery namedTraceConfigurationQuery = new NamedTraceConfigurationQuery { Names = new List<string> { "Upstream"} };
IReadOnlyList<NamedTraceConfiguration> namedTraceConfigurations = traceManager.GetNamedTraceConfigurations(namedTraceConfigurationQuery);
NamedTraceConfiguration upstreamTraceConfiguration = namedTraceConfigurations.FirstOrDefault();
Tracer upstreamTracer = traceManager.GetTracer(upstreamTraceConfiguration);
}
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.
The TraceArgument
class consolidates trace parameters. Different tracers, including custom tracers, may subclass TraceArgument
. The base TraceArgument
class contains properties for starting locations, barriers, filter barriers, result types, and a trace configuration.
Starting locations are Elements that specify the starting point of a trace, while barriers and filter barriers (both also Elements) prevent a trace from continuing.
Barriers work by preventing traversability, which is done during the initial phase of a trace. With subnetwork-based traces, they can prevent traversability to the subnetwork controller. One use case for a barrier from the electric distribution domain would be placing a barrier on a switch to indicate that the switch is open. This would prevent electricity from reaching areas downstream of that switch.
Filter barriers are evaluated in a later pass of the tracing algorithm, and can be used to restrict the returned result set without impacting the ability to find the subnetwork controller from a starting point. One use case for a filter barrier from the gas distribution domain would be using a filter barrier to simulate the location of a squeeze-off when executing a valve isolation trace. The user could place a filter barrier at the proposed location of the squeeze-off and then verify its impact.
In the absence of barrier and filter barrier elements, users would have switch to a version, edit features, and then validate network topology in order to perform this kind of what-if analysis.
The trace configuration is explained later in this topic.
The ResultTypes
property allows you to specify one or more result formats to return. Currently implemented values are Element
, which returns a set of elements, and FunctionValue
, which returns a set of function calculation results.
Future software versions may implement additional result types. Possible result types that may be supported in the future include propagator values per row, connectivity information, network diagrams, geometry, and additional network attributes.
The following snippet shows how to create a TraceArgument
object:
IReadOnlyList<Element> startingPointList = new List<Element>();
// Code to fill in list of starting points goes here...
TraceArgument traceArgument = new TraceArgument(startingPointList);
TraceConfiguration traceConfiguration = new TraceConfiguration();
// Code to fill in trace configuration goes here...
traceArgument.Configuration = traceConfiguration;
TraceArgument
objects can also be created with a NamedTraceConfiguration.
using (TraceManager traceManager = utilityNetwork.GetTraceManager())
{
IReadOnlyList<Element> startingPoints = new List<Element>();
// Returns all named trace configurations
IReadOnlyList<NamedTraceConfiguration> namedTraceConfigurations = traceManager.GetNamedTraceConfigurations(new NamedTraceConfiguration());
NamedTraceConfiguration namedTraceConfiguration = namedTraceConfigurations.FirstOrDefault();
// Initializes a new instace of trace argument object using a named trace configuration and a set of elements
TraceArgument traceArgument = new TraceArgument(namedTraceConfiguration, startingPoints);
}
SubnetworkTracer
can also support a TraceArgument
object that is created using a Subnetwork
parameter. In this case, the subnetwork's controllers are used as starting points.
The TraceConfiguration object contains all of the other inputs to a trace.
The properties in the TraceConfiguration object can be categorized as follows:
The following table lists the basic properties in the TraceConfiguration object and their descriptions:
Property | Description |
---|---|
AllowIndeterminateFlow | Determines whether elements inside a loop are returned with subnetwork trace results. The default is true. |
IncludeContainers | Includes containers of trace results in the result set. IncludeContainers is recursive. If set to true, if a result element is inside a nested container, both containers are returned. The default is false. |
IncludeContent | Includes content of trace results in the result set. IncludeContent is recursive. If set to true, if a result element contains other elements that are themselves containers, the contents of the nested containers are also returned. The default is false. |
IncludeStructures | Includes structures with attachments that are trace results in the result set. The default is false. |
IncludeBarriersWithResults | Determines whether the row that met one of the barrier criterion (described in the Traversability and Filter sections below) is included in the trace results. For example, it is typical to include an open switch when performing an electric circuit trace, so in these cases, the property should be set to True . |
IncludeIsolatedFeatures | Determines whether or not the isolated features are returned with trace results. The default is false, which only returns those features that stop the trace (e.g., valves). |
IgnoreBarriersAtStartingPoint | Determines whether or not barriers at starting points are ignored. One typical use case is performing an upstream trace, stopping at each protective device. Each subsequent trace would use as a starting point the end point of the previous trace. Without this setting, subsequent traces would stop at the starting point, and not return any results. The default is false. |
ShortestPathNetworkAttribute | This property is only used by ShortestPathTracer and is used by that tracer to determine the shortest path. Typically, shape length is used. However, other network attributes can be used to calculate cost. For example, if you are looking for the shortest path through an underground system, you might prefer a longer path via duct banks versus a shorter path through a trench that requires excavation. |
DomainNetwork | The DomainNetwork property is required and only used with subnetwork-based tracers. |
SourceTier | This property is only used with subnetwork-based traces. If DomainNetwork represents a partitioned network, the SourceTier property is optional. If DomainNetwork represents a hierarchical network, the SourceTier property is required. Since rows in hierarchical networks can belong to multiple tiers, this property notifies the tracer which tier to use for tracing. If this property is included, the trace code will perform an additional check to validate that the starting points and barriers belong to this tier. |
TargetTier | If this optional property is included, upstream and downstream traces will continue into the specified tier. If the property is null, upstream and downstream traces will stop in the current tier (that is, at subnetwork controllers). |
UseDigitizedDirection | Indicates whether a trace will use the flow direction. The default is False . |
ValidateConsistency | If set to true, the trace code will perform additional checks. If the trace encounters a dirty area, an exception is thrown from Trace() . The default is True . |
IncludeContainers
and IncludeStructures
are transitive. In other words, if they are both true, if a container contains a result element and that container is attached to a structure, the structure is returned even if the result element is not directly attached to the structure.
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
andOr
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.
The Condition
class is an object that evaluates to true or false. Currently there is a single subclass of Condition
called ConditionalExpression
.
If the visited row meets the criteria of the comparison, traversal is stopped. If the visited row does not meet the criteria, that row is not included in the results. A null condition always permits traversal.
There are two basic comparisons as follows:
- The
NetworkAttributeComparison
class is based on comparison with a network attribute. This network attribute can be compared to a specific non-null value or a second network attribute.
- The
CategoryComparison
class checks the asset type of the row to establish if it implements a specific category.
These filters can be combined using the And
and Or
classes to form more complex filters.
The following code shows how to build a condition that stops when traversal encounters a row with a Lifecycle network attribute that contains a value other than InDesign or InService.
const int InDesign = 4;
const int InService = 8;
using (NetworkAttribute lifecycleNetworkAttribute = utilityNetworkDefinition.GetNetworkAttribute("Lifecycle"))
{
// Create a NetworkAttributeComparison that stops traversal if Lifecycle <> "In Design" (represented by the constant InDesign)
NetworkAttributeComparison inDesignNetworkAttributeComparison = new NetworkAttributeComparison(lifecycleNetworkAttribute, Operator.NotEqual, InDesign);
// Create a NetworkAttributeComparison to stop traversal if Lifecycle <> "In Service" (represented by the constant InService)
NetworkAttributeComparison inServiceNetworkAttributeComparison = new NetworkAttributeComparison(lifecycleNetworkAttribute, Operator.NotEqual, InService);
// Combine these two comparisons together with "And"
And lifecycleFilter = new And(inDesignNetworkAttributeComparison, inServiceNetworkAttributeComparison);
// Final condition stops traversal if Lifecycle <> "In Design" and Lifecycle <> "In Service"
traceConfiguration.Traversability.Barriers = lifecycleFilter;
}
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 };
}
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 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.
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.
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.
Trace results can be filtered with conditions or a list of asset types. Output filtering is applied as the final step of the tracing process. It occurs after traversability, filtering, and function calculations.
Output filtering is defined through two properties on TraceConfiguration
: OutputCondition
and OutputAssetTypes
. OutputCondition
is the same Condition
class used elsewhere in the tracing API. It can be based on comparisons against network attributes, categories, or both. OutputAssetTypes
allows a set of asset types to be used to filter. Using CategoryComparison
with the OutputCondition
property is the preferred technique to avoid hardcoding your add-in to a particular data model as shown below.
// Create an output category to filter the trace results to only include
// features with the "Service Point" category assigned
traceConfiguration.OutputCondition = new CategoryComparison(CategoryOperator.IsEqual, "Service Point");
If both types of output filters are provided, both filters must be satisfied for the row to be included in the result set. If both properties are null, no additional filtering takes place.
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 };
}
A named trace configuration is a predefined set of trace options to simplify configuring and running a trace operation in the utility network. It allows clients to perform trace analytics without needing to understand the data model. Client applications can simply allow the user to choose from a set of named trace configurations that provide tracing capabilities. The set can be configured for different workflows, and a single application can be used by multiple customers and even across multiple problem domains.
Named traced configurations are built with ArcGIS Pro, typically stored on ArcGIS Enterprise, and shared through web maps.
The named trace configurations associated with a utility network layer can accessed by calling the GetNamedTraceConfigurations()
method on the UtilityNetworkLayer
class.
IReadOnlyList<NamedTraceConfiguration> GetNamedTraceConfigurationsFromLayer(UtilityNetworkLayer utilityNetworkLayer)
{
// Returns a set of named traced configurations associated with the layer
IReadOnlyList<NamedTraceConfiguration> namedTraceConfigurations = utilityNetworkLayer.GetNamedTraceConfigurations();
return namedTraceConfigurations;
}
The named trace configruations can also be obtained from a TraceManager
as shown in the following code:
using (TraceManager traceManager = utilityNetwork.GetTraceManager())
{
// Query to find named trace configurations whose name is 'UpstreamTrace'
NamedTraceConfigurationQuery namedTraceConfigurationQuery = new NamedTraceConfigurationQuery {Names = new List<string> { “UpstreamTrace” } };
// A set of named trace configurations specified by the named traced configuration query
IReadOnlyList<NamedTraceConfiguration> namedTraceConfigurations = traceManager.GetNamedTraceConfigurations(namedTraceConfigurationQuery);
}
The NamedTraceConfigurationQuery
class defines a filter to find named trace configurations based on creators, GlobalIDs, names, or tags.
The Trace()
method on Tracer
classes returns a set of Result
objects. One Result
object is returned for each ResultType
specified in TraceArgument.ResultTypes
.
Result
is an abstract class. One distinct subclass exists for each member of the ResultTypes
enum.
The Result
base class has a single property. If the TraceConfiguration.Filter
object contained a NearestNeighbor
object, the NearestNeighborResult
property returns whether or not the correct number of rows were returned.
The current release supports the following subclasses of Result
:
-
ElementResult
contains a list ofElement
objects that meet the tracing criteria. -
FeatureElementResult
contains a list ofFeatureElement
objects that meet the tracing criteria. These objects are used to include network attribute and other field values with the trace result. -
FunctionOutputResult
contains a list of FunctionOutput objects. OneFunctionObject
is returned for each function assigned toTraceConfiguration.Functions
. TheFunctionOutput
class is a set of function-value pairs. -
AggegatedGeometryResult
contains a list of geometries. TheLine
property contains the union of all of the linear features included in the result set. Likewise, thePoint
property contains the union of all of the point features, and thePolygon
property contains the union of all of the polygonal features included in the result set. Returning geometries requires ArcGIS Enterprise 10.8.1 or later.
This code shows how to get the result of the function that was created earlier in this topic to sum the Load attribute.
// Get the FunctionOutcomeResult from the trace results
FunctionOutputResult functionOutputResult = traceResults.OfType<FunctionOutputResult>().First();
// First() can be used here because only one Function was included in the TraceConfiguration.Functions collection
FunctionOutput functionOutput = functionOutputResult.FunctionOutputs.First();
// Extract the total load from the GlobalValue property
double totalLoad = (double)functionOutput.Value;
The trace results can be exported in the same way you export subnetworks by using the Export()
method in the Tracer
class. The export operation constructs JSON result based on the parameters defined in the TraceArgument
and the TraceExportOptions
classes and saves it at the provided path URI. The JSON format allows an easy integration with third-party systems for outage or distribution management.
// Set export options
TraceExportOptions exportOptions = new TraceExportOptions()
{
ServiceSynchronizationType = ServiceSynchronizationType.Asynchronous,
IncludeDomainDescriptions = true,
};
// Path to save trace results
string jsonPath = $"{Path.GetTempPath()}TraceResults.json";
Uri jsonUri = new Uri(jsonPath);
// Execute export
downstreamTracer.Export(jsonUri, traceArgument, exportOptions);
string jsonAbsolutePath = HttpUtility.UrlDecode(jsonUri.AbsolutePath);
if (jsonUri.IsFile && File.Exists(jsonAbsolutePath))
{
// Work with the JSON
}
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);
}
}
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.
A TraceConfiguration
is obtained from the GetTraceConfiguration
method on the Tier
class. It is assigned by the Set Subnetwork Definition geoprocessing tool where it defines the defaults used by subnetwork traces, updates, and exports.
Not all of the TraceConfiguration properties apply in this scenario. Nonapplicable properties are left empty as shown in the following table:
Property | Applicable |
---|---|
DomainNetwork | |
Filter | |
Functions | ✔ |
IgnoreBarriersAtStartingPoint | |
IncludeBarriersWithResults | ✔ |
IncludeContainers | |
IncludeContent | |
IncludeIsolatedFeatures | |
IncludeStructures | |
OutputAssetTypes | |
OutputCondition | |
Propagators | ✔ |
ShortestPathNetworkAttribute | |
SourceTier | |
TargetTier | |
Traversability | ✔ |
ValidateConsistency |
Propagated values are stored on domain network feature classes during the Update Subnetwork process. The field used is designated by the Propagator.PersistedField
property.
Similarly, the Function
property is used to store subnetwork summaries. These are computed during Update Subnetwork and stored on the SubnetLine
feature class. The field used is designated by the Function.PersistedField
property.
Subnetwork.Update()
typically uses the default tracing configuration represented by Tier.GetTraceConfiguration()
. However, an override of Subnetwork.Update()
allows this default trace configuration to be overridden.
Not all the TraceConfiguration properties apply in this scenario as shown in the following table below:
Property | Applicable |
---|---|
DomainNetwork | |
Filter | |
Functions | |
IgnoreBarriersAtStartingPoint | |
IncludeBarriersWithResults | ✔ |
IncludeContainers | |
IncludeContent | |
IncludeStructures | |
OutputAssetTypes | |
OutputCondition | |
Propagators | ✔ |
ShortestPathNetworkAttribute | |
SourceTier | |
TargetTier | |
Traversability | ✔ |
ValidateConsistency |
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.
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.
To create a button on the ribbon that works with utility network layers, use the utility network condition in your DAML as follows:
condition="esri_mapping_utilityNetworkCondition"
This will cause the button to be enabled if a utility network is present in the map.
This behavior changed at Pro 2.3. Prior to Pro 2.3, a different condition (esri_mapping_utilityNetworkLayerSelectedCondition
) would only enable your button if any of the following layers are selected:
- Utility network layer
- Feature layer that corresponds to a feature class that belongs to a utility network
- Subtype group layer that corresponds to a feature class that belongs to a utility network
This old condition has been removed, and we recommend revisiting your code to make sure that it will continue to work with the new contract. If your code continues to use esri_mapping_utilityNetworkLayerSelectedCondition
, your button will always be enabled with Pro 2.3 and later releases.
For more information on creating a button or other add-in, see the conceptual documentation.
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
.
The EditOperation
class has been extended with methods to create and delete associations. New overloads to EditOperation.Create()
and EditOperation.Delete()
take association description objects. These objects describe a connectivity, containment, or structure attachment association to create or delete.
As shown in the class diagram above, association description objects are created with RowHandle objects. The RowHandle
class describes a row that is involved in the creation or deletion of an association. Row handles can be created using the following inputs:
- UtilityNetwork and Element
- MapMember and GlobalID
- MapMember and ObjectID
- Row
- RowToken (described in the following section)
Creating an association description with RowHandle
requires the global IDs of both features. Creating a RowHandle with only an ObjectID could cause an additional query to fetch this GlobalID. Therefore, using the other constructors is preferred.
The following code snippet demonstrates how to build a structural attachment association between a pole and a transformer bank:
// Create edit operation
EditOperation editOperation = new EditOperation();
editOperation.Name = "Create structural attachment association";
// Create a RowHandle for the pole
Element poleElement = utilityNetwork.CreateElement(poleAssetType, poleGlobalID);
RowHandle poleRowHandle = new RowHandle(poleElement, utilityNetwork);
// Create a RowHandle for the transformer bank
Element transformerBankElement = utilityNetwork.CreateElement(transformerBankAssetType, transformerBankGlobalID);
RowHandle transformerBankRowHandle = new RowHandle(transformerBankElement, utilityNetwork);
// Attach the transformer bank to the pole
AssociationDescription structuralAttachmentAssociationDescription = new AssociationDescription(AssociationType.Attachment, poleRowHandle, transformerBankRowHandle);
editOperation.Create(structuralAttachmentAssociationDescription);
editOperation.Execute();
There are many different constructors for the AssociationDescription
class. The following table shows which constructors can be used with each association type.
Association Type | Constructors |
---|---|
Containment |
AssociationDescription(AssociationType, RowHandle, RowHandle, isContainmentVisible: bool) AssociationDescription(AssociationType, RowHandle, RowHandle) Sets containment to be invisible |
Structural Attachment | AssociationDescription(AssociationType, RowHandle, RowHandle) |
Junction-Junction |
AssociationDescription(AssociationType, RowHandle, RowHandle) AssociationDescription(AssociationType, RowHandle, terminalID1: long, RowHandle) The first junction uses terminals AssociationDescription(AssociationType, RowHandle, RowHandle, terminalID2: long) The second junction uses terminals AssociationDescription(AssociationType, RowHandle, terminalID1: long, RowHandle, terminalID2: long) Both junctions use terminals |
Junction-Edge Object Connectivity From Side Junction-Edge Object Connectivity To Side |
AssociationDescription(AssociationType, RowHandle, RowHandle) AssociationDescription(AssociationType, RowHandle, terminalID1: long, RowHandle) The first junction uses terminals. Junction-edge object connectivity associations always specify the junction first |
Junction-Edge Object Connectivity Midspan |
AssociationDescription(AssociationType, RowHandle, RowHandle, percentAlong: double) Midspan junction-edge object connectivity associations do not support terminals. The percentAlong value should be between 0.0 and 1.0 (exclusive) |
It is often desirable to create features and the associations between them in the same edit operation. This ensures that only one entry is created in the undo/redo stack and may result in fewer calls to the feature service. At first glance, it might appear that EditOperation.Create()
provides an Action overload that returns the ObjectID, and this could be used to create an association. Remember, however, that all of the edits specified in an EditOperation are not actually run until Execute()
is called. Therefore the ObjectIDs are not yet available in the edit operation.
Instead, there are methods on EditOperation
for feature creation called CreateEx()
. These methods work in the same way as Create()
but return a RowToken. The RowToken object represents a row to be created. These RowTokens can be used to create RowHandles, which are in turn passed to association description objects.
The following code snippet shows how to create a pole, a transformer bank, and a structural attachment association between the two in a single edit operation:
// Create an EditOperation
EditOperation editOperation = new EditOperation();
editOperation.Name = "Create pole; create transformer bank; attach transformer bank to pole";
// Create the transformer bank
RowToken transformerBankToken = editOperation.CreateEx(transformerBankLayer, transformerBankAttributes);
// Create a pole
RowToken poleToken = editOperation.CreateEx(poleLayer, poleAttributes);
// Create a structural attachment association between the pole and the transformer bank
RowHandle poleHandle = new RowHandle(poleToken);
RowHandle transformerBankHandle = new RowHandle(transformerBankToken);
AssociationDescription poleAttachment = new AssociationDescription(AssociationType.Attachment, poleHandle, transformerBankHandle);
editOperation.Create(poleAttachment);
// Execute the EditOperation
editOperation.Execute();