The ArcGIS Maps SDK for JavaScript ArcGIS Maps SDK for JavaScript, previously known as ArcGIS API for JavaScript, is a developer product for building mapping and spatial analysis applications for the web. Learn more supports telecom domain networks in utility networks. This page shows how to implement telecom workflows in JavaScript, including circuit management, unit identifier management, tracing, association editing, and divide/combine operations.

Examples on this page assume the UtilityNetwork instance is created using a dataset which includes a telecom domain network.

If you run into issues while implementing telecom capabilities, share feedback through Esri support channels.

Getting started

The UtilityNetwork class is the entry point to most telecom-specific workflows. From an instance of UtilityNetwork loaded with a telecom domain network, we create a CircuitManager for circuit management workflows, and a UnitIdentifierManager for unit identifier management workflows.

const [UtilityNetwork] =
await $arcgis.import([
"@arcgis/core/networks/UtilityNetwork.js",
]);
const utilityNetwork = new UtilityNetwork({
layerUrl: "https://host.com/arcgis/rest/services/Test/FeatureServer/0",
});
await utilityNetwork.load();
const domainNetworks = utilityNetwork.dataElement?.domainNetworks;
const telecomDomainNetwork = domainNetworks!.find((dn) => dn.isTelecomNetwork);
const circuitManager = await utilityNetwork.getCircuitManager(
telecomDomainNetwork.domainNetworkName
);
const unitIdentifierManager = await utilityNetwork.getUnitIdentifierManager();

Telecom-specific classes and capabilities

The following classes have telecom-specific capabilities or exist for use with telecom domain networks.

The relationship between these telecom-specific classes in the Maps SDK for JavaScript is visualized in the Telecom Object Model Diagram (OMD).

UtilityNetwork class telecom capabilities

The UtilityNetwork class is the entry point for telecom workflows in a utility network. The UtilityNetwork class exposes telecom-specific properties and methods used for editing and tracing scenarios in a telecom domain network.

To create an instance of CircuitManager for circuit management of a telecom domain network:

const circuitManager = await utilityNetwork.getCircuitManager(
telecomDomainNetwork.domainNetworkName
);

To create an instance of UnitIdentifierManager for unit identifier management of telecom domain networks:

const unitIdentifierManager = await utilityNetwork.getUnitIdentifierManager();

Circuit management

The CircuitManager class handles circuit lifecycle operations for a specific telecom domain network in a utility network. It supports creating, altering, querying, verifying, exporting, and deleting circuits. It also provides references to circuit-related tables (circuits, sections, and subcircuits).

For background on the telecom data model with respect to circuits, see Interacting with Circuits.

Circuit management workflows may involve the following:

  • Create or modify a circuit definition (nonsectioned or sectioned).
  • Validate or verify the circuit to confirm expected path behavior.
  • Query circuits as data changes over time.

Circuits are modeled in the Maps SDK for JavaScript with the following classes:

  • Circuit: Represents a circuit in a telecom domain network and is used to manage circuits with CircuitManager. A Circuit instance can have start and stop locations or sections, subcircuits, and user attributes. Circuit instances also have methods to help with setting and reading the values of these properties. Circuits must be instantiated using either start/stop locations or sections; combining both is invalid. Circuits can also persist a global ID by specifying a globalId property during circuit creation.
  • CircuitSection: Represents a circuit section in a telecom domain network. Sections can be physical or virtual, can either use start and stop locations or consume a subcircuit, and support user attributes. The section will also have a path if returned as the result of a circuit trace on a sectioned circuit. A CircuitSection must be instantiated using either start/stop locations or a subcircuit; combining both is invalid. Circuit sections can also persist a global ID by specifying a globalId property during circuit creation.
  • Subcircuit: Represents a circuit subcircuit in a telecom domain network. A Subcircuit can be reserved and can have user attributes. Subcircuits can also persist a global ID by specifying a globalId property during circuit creation.
  • CircuitLocation: Represents a circuit start or stop location in a telecom domain network. Instances of this class are used for start and stop locations on Circuit and CircuitSection instances, and to query circuits with queryCircuits.

Example: create and verify a non-sectioned circuit

To create a non-sectioned circuit with user-defined attributes, then verify the circuit:

const circuit = new Circuit({
name: "CIR-1001",
circuitType: "physical",
startLocation: new CircuitLocation({
sourceId: 27,
globalId: "{A3F2B9D1-7C4E-4A8D-91F3-2E7B6C5D9A10}",
firstUnit: 1,
lastUnit: 1,
}),
stopLocation: new CircuitLocation({
sourceId: 27,
globalId: "{B4C1E8F0-2D6A-43BE-8F19-6A2D3E9C7B55}",
firstUnit: 1,
lastUnit: 1,
}),
attributes: {
serviceType: "business-data",
},
});
await circuitManager.create(circuit);
const verifyResult = await circuitManager.verify({
circuitNames: ["CIR-1001"],
});

Use verify after create or alter operations to check the validity of circuits in a telecom domain network before moving to downstream analysis and tracing. See CircuitVerifyResult for the shape of verifyResult.

Example: create and verify a sectioned circuit

To create a sectioned circuit, provide a Map to represent the logical connectivity between sections in a circuit. This map should represent a directed adjacency list where each key is a CircuitSection that connects to other CircuitSection instances indicated by the key’s value. The map is type Map<CircuitSection, CircuitSection[]>.

In this example, section 1 connects to 2 and 3 (where sections 2 and 3 are in parallel), 2 connects to 4, 3 connects to 4, and 4 connects to nothing (is the end of the circuit).

const sectionOne = new CircuitSection({
sectionId: 1,
sectionType: "physical",
startLocation: new CircuitLocation({
sourceId: 20,
globalId: "{6B0A0E4E-80EF-4688-B463-253063A5B4B2}",
firstUnit: 1,
lastUnit: 4,
}),
stopLocation: new CircuitLocation({
sourceId: 20,
globalId: "{3A557124-4243-4615-96DF-51991E6B9766}",
firstUnit: 1,
lastUnit: 4,
}),
});
const sectionTwo = new CircuitSection({
sectionId: 2,
sectionType: "physical",
subcircuit: new Subcircuit({ name: "SUBCIRCUITVALUE2" }),
});
const sectionThree = new CircuitSection({
sectionId: 3,
sectionType: "physical",
startLocation: new CircuitLocation({
sourceId: 20,
globalId: "{7C874691-D659-4311-B19C-D33027B6F48A}",
firstUnit: 1,
lastUnit: 4,
}),
stopLocation: new CircuitLocation({
sourceId: 20,
globalId: "{BF34887C-359E-4E00-A695-8D92BF41FE37}",
firstUnit: 1,
lastUnit: 4,
}),
});
const sectionFour = new CircuitSection({
sectionId: 4,
sectionType: "physical",
startLocation: new CircuitLocation({
sourceId: 20,
globalId: "{E4ECA4B4-F9F7-4AB2-B2FD-6CDAB1A9506B}",
firstUnit: 1,
lastUnit: 4,
}),
stopLocation: new CircuitLocation({
sourceId: 20,
globalId: "{18DE7259-FDC6-4788-B13B-3F6210CCCB56}",
firstUnit: 1,
lastUnit: 4,
}),
});
const sections = new Map([
[sectionOne, [sectionTwo, sectionThree]],
[sectionTwo, [sectionFour]],
[sectionThree, [sectionFour]],
[sectionFour, []],
]);
const sectionedCircuit = new Circuit({
name: "SectionedCircuit",
circuitType: "physical",
sections: sections,
});
await circuitManager.create(sectionedCircuit);
const verifyResult = await circuitManager.verify({
circuitNames: ["SectionedCircuit"],
});

Example: query circuits

CircuitManager supports multiple query patterns depending on what identifiers you already have in your workflow.

// Query circuits by name; returns `Circuit` class instances
const circuitsByName = await circuitManager.queryCircuits({
names: ["CIR-1001"],
});
// Query circuits by global ID; returns `Circuit` class instances
const circuitsByIds = await circuitManager.queryCircuits({
globalIds: ["{8A1B5A33-3EBA-42CC-9085-31B22DAFE5DC}"],
});
// Query circuits that begin or end at the given `locations`
// `locationType` specifies the type of circuit location to query ("start", "stop", or "all")
// Returns `Circuit` class instances
const circuitsByLocations = await circuitManager.queryCircuits({
locationType: "all",
locations: [
new CircuitLocation({
sourceId: 20,
globalId: "{8A1B5A33-3EBA-42CC-9085-31B22DAFE5DC}",
firstUnit: 1,
lastUnit: 1,
}),
],
});
// Query circuit names; returns an array of strings
const circuitNames = await circuitManager.queryCircuitNames({
locationType: "all",
locations: [
new CircuitLocation({
sourceId: 20,
globalId: "{8A1B5A33-3EBA-42CC-9085-31B22DAFE5DC}",
firstUnit: 1,
lastUnit: 1,
}),
],
});

Example: alter a circuit

Query an existing circuit, alter the circuit, then verify.

const circuits = await circuitManager.queryCircuits({
names: ["CIR-1001"],
});
const circuitToAlter = circuits[0];
circuitToAlter.name = "CIR-1001-UPDATED";
await circuitManager.alter(circuitToAlter);
const verifyResult = await circuitManager.verify({
circuitNames: ["CIR-1001-UPDATED"],
});

Example: export circuits

Use export to export feature and connectivity information about circuits into JSON files for consumption by external systems. Export operations are commonly used after circuits have been verified and before sharing or downstream processing.

const exportResult = await circuitManager.export({
circuitNames: ["CIR-1001", "CIR-1002"],
exportAcknowledgement: true,
outSpatialReference: { wkid: 4326 },
resultTypes: [
{
type: "features",
includeGeometry: true,
includeDomainDescriptions: true,
networkAttributeNames: ["Asset group", "Asset type"],
diagramTemplateName: "",
resultTypeFields: [
{ networkSourceId: 20, fieldName: "FIRSTUNIT" },
{ networkSourceId: 20, fieldName: "LASTUNIT" },
],
},
],
});

See CircuitExportResult for the shape of exportResult.

Example: delete circuits

Use delete to logically remove one or more circuits by name.

In this context, logically deleted means the circuit is marked with status: "deleted" in the circuit management tables, but the record is still present in the Circuit table until a follow-up export workflow acknowledges and removes it. See Circuit management tables and Circuits.

await circuitManager.delete(["CIR-1001", "CIR-1002"]);

To fully remove a logically deleted circuit record (physical delete), perform the following:

  1. Logically delete with CircuitManager.delete(...).
  2. Optionally, confirm the circuit is now deleted by querying with status: "deleted".
  3. Export with exportAcknowledgement: true for the deleted circuit name(s).
// Optionally, confirm that the circuit was logically deleted
const deletedCircuits = await circuitManager.queryCircuits({
names: ["CIR-1001"],
status: "deleted",
});
// Acknowledged export physically removes deleted circuit rows
await circuitManager.export({
circuitNames: deletedCircuits.map((circuit) => circuit.name),
exportAcknowledgement: true,
outSpatialReference: { wkid: 4326 },
resultTypes: [
{
type: "features",
includeGeometry: false,
includeDomainDescriptions: false,
networkAttributeNames: [],
diagramTemplateName: "",
resultTypeFields: [],
},
],
});

In production workflows, query and verify target circuits before deleting to avoid removing active operational circuits.

For asynchronous execution patterns, use submitVerifyJob and submitExportJob.

Unit identifier management

The UnitIdentifierManager class provides unit identifier management functionality for telecom domain networks in a utility network. It supports querying, reserving, resizing, and resetting ranges for grouped elements, e.g., fibers in a cable or ports in a panel. This class can also generate FeatureService.applyEdits payloads for dividing and combining grouped elements.

The concept of grouping is central to the telecom domain network data model. Instead of storing one row per unit in a table, grouped objects represent a contiguous range with firstUnit and lastUnit. Unit identifier operations allocate and maintain those ranges.

For background on the telecom data model with respect to unit identifiers and grouping, see Work with unit identifiers and Grouping of objects.

Most unit identifier operations are performed with objects typed as UnitIdentifier, which has a source ID and global ID for the grouped element in the telecom domain network.

Example: query unit identifiers

const containers = [
{
sourceId: 16,
globalId: "{25B5F11B-70A2-4A61-9284-F0CE4C1E2111}",
},
];
const queryResult = await unitIdentifierManager.query(containers);

Example: query the content of a container and resize the content

Query a unit container and resize its content to change the number of units associated with the content feature to 15 units.

Resizing is useful when the number of units on an existing unit identifiable feature needs to change after initial setup. Use it to increase or decrease the feature’s lastUnit value so the grouped unit range reflects the current design or edited state of the network.

In a typical unit identifier workflow, a TelcoDevice can contain a unit identifiable transceiver (TRX) that starts with units 1 through 8 (8 ports). If the transceiver hardware is replaced with a higher-density model, resize the grouped feature by increasing lastUnit (for example, from 8 to 15) so the unit range reflects the updated equipment design and subsequent associations and traces use the correct unit space.

const containers = [
{
sourceId: 16,
globalId: "{25B5F11B-70A2-4A61-9284-F0CE4C1E2111}",
},
];
const queryResult = await unitIdentifierManager.query(containers);
const content = queryResult[0].ranges[0].content;
await unitIdentifierManager.resize(content, 15);

Example: reserve a unit range

Reserve units 2 through 5 (inclusive) for a unit container.

Reserving a range is useful when you intentionally need a gap in the unit ID sequence. A common case is preparing future slot or port positions so upcoming containment edits receive the expected unit numbers, even when other editors are adding content at the same time.

const container = {
sourceId: 16,
globalId: "{25B5F11B-70A2-4A61-9284-F0CE4C1E2111}",
};
await unitIdentifierManager.reserve(container, 2, 5);

Example: reset containers

Resets the unit identifiers associated with the container.

Reset is useful when you need to condense the unit ID space by removing gaps that accumulate over time (for example, after card or port replacement), or when unit identifiable records become out of sync with the container and you need to restore a consistent sequence.

const containers = [
{
sourceId: 16,
globalId: "{25B5F11B-70A2-4A61-9284-F0CE4C1E2111}",
},
];
await unitIdentifierManager.reset(containers);

Tracing in a telecom domain network

Telecom domain networks extend utility network tracing with path and circuit traces. Path traces find valid paths that exist between one or more start and stop locations; in a telecom domain network, those start and stop locations may be grouped elements. Circuit traces use network topology and circuit management tables to identify the elements which comprise a circuit.

The Maps SDK for JavaScript supports path and circuit traces via the following:

Example: run a path trace

const unTraceConfiguration = new UNTraceConfiguration({
numPaths: 2,
maxHops: 100,
domainNetworkName: "Telco",
});
const traceParameters: TraceProperties = {
traceType: "path",
traceLocations: [
{
type: "starting-point",
globalId: "{49FCE773-6C0D-4029-B680-394E591CD002}",
percentAlong: 0.5,
firstUnit: 1,
lastUnit: 1,
},
{
type: "stopping-point",
globalId: "{FFEC062E-E871-491C-BA70-031F8628C7A1}",
firstUnit: 1,
lastUnit: 1,
},
],
traceConfiguration: unTraceConfiguration,
resultTypes: [
{
type: "paths",
includeGeometry: false,
includePropagatedValues: false,
networkAttributeNames: [],
diagramTemplateName: "",
resultTypeFields: [],
},
],
};
const traceResult = await utilityNetwork.trace(traceParameters);

The result of the path trace includes paths instead of elements in TraceResult.paths.

Example: run a circuit trace

Running a circuit trace on a physical, non-sectioned circuit:

const unTraceConfiguration = new UNTraceConfiguration({
circuitName: "CIR-1001",
domainNetworkName: "Telco",
});
const traceParameters: TraceProperties = {
traceType: "circuit",
traceConfiguration: unTraceConfiguration,
resultTypes: [
{
type: "circuits",
includeGeometry: false,
includePropagatedValues: false,
networkAttributeNames: [],
diagramTemplateName: "",
resultTypeFields: [],
},
],
};
const traceResult = await utilityNetwork.trace(traceParameters);

The result of the circuit trace includes circuits instead of elements in TraceResult.circuits.

Associations in a telecom domain network

In a telecom domain network, associations can be modeled using foreign-key fields on domain network classes. To support foreign key-based associations in the Maps SDK for JavaScript, Association allows its globalId property to be null. If an Association represents a foreign key-based relationship, its globalId property will be null, as there is no explicit association record to populate globalId.

The associations table also includes attributes for grouping. To support workflows involving grouped elements, fromNetworkElement and toNetworkElement on Association can be TelecomNetworkElement, which extends NetworkElement with grouping properties firstUnit and lastUnit.

Example: add associations between grouped elements

The FeatureService.applyEdits payload for adding associations supports grouped unit ranges. The UtilityNetwork.generateAddAssociations helper can generate this payload.

To add an association between grouped elements:

const association = new Association({
fromNetworkElement: new TelecomNetworkElement({
globalId: "{931A1CD0-BF9B-43B1-B4B9-5BE4157DAA96}",
networkSourceId: 27,
firstUnit: 1,
lastUnit: 3,
}),
toNetworkElement: new TelecomNetworkElement({
globalId: "{E26E101E-92EA-4195-A686-CFD69D0E8CAB}",
networkSourceId: 27,
firstUnit: 4,
lastUnit: 6,
}),
associationType: "connectivity",
});
const addAssociationPayload = utilityNetwork.generateAddAssociations([association]);
await featureService.applyEdits([addAssociationPayload]);

Example: delete associations between grouped elements

The FeatureService.applyEdits payload supports deleting both traditional and foreign-key associations with the deleteAssociations field. The UtilityNetwork.generateDeleteAssociations helper supports creating payloads to delete either type of association.

To delete a foreign key-based association:

const association = new Association({
fromNetworkElement: new TelecomNetworkElement({
globalId: "{931A1CD0-BF9B-43B1-B4B9-5BE4157DAA96}",
networkSourceId: 27,
firstUnit: 1,
lastUnit: 3,
}),
toNetworkElement: new TelecomNetworkElement({
globalId: "{E26E101E-92EA-4195-A686-CFD69D0E8CAB}",
networkSourceId: 27,
firstUnit: 4,
lastUnit: 6,
}),
associationType: "connectivity",
});
const deleteAssociationPayload = utilityNetwork.generateDeleteAssociations([association]);
await featureService.applyEdits([deleteAssociationPayload]);

Example: delete a traditional association with generateDeleteAssociations

To delete a traditional association with an explicit association record, include the association globalId in addition to fromNetworkElement and toNetworkElement.

const association = new Association({
globalId: "{210A7912-46F5-4FB3-A46B-D000014BDE43}",
fromNetworkElement: new TelecomNetworkElement({
globalId: "{931A1CD0-BF9B-43B1-B4B9-5BE4157DAA96}",
networkSourceId: 27,
firstUnit: 1,
lastUnit: 3,
}),
toNetworkElement: new TelecomNetworkElement({
globalId: "{E26E101E-92EA-4195-A686-CFD69D0E8CAB}",
networkSourceId: 27,
firstUnit: 4,
lastUnit: 6,
}),
associationType: "connectivity",
});
const deleteAssociationPayload = utilityNetwork.generateDeleteAssociations([association]);
await featureService.applyEdits([deleteAssociationPayload]);

Example: delete traditional association by GUID (legacy)

For backwards compatibility, deleting traditional associations by association GUID to the deleteFeatures field is still supported. This method requires specifying the globalIdUsed option when calling applyEdits.

const deleteTraditionalAssociationPayload = {
deleteFeatures: [
{
globalId: "{B8192CAA-98D5-4D42-BCF4-870CDD04B48F}",
},
],
id: 500001,
identifierFields: { globalIdField: "globalid", objectIdField: "objectid" },
};
await featureService.applyEdits([deleteTraditionalAssociationPayload], {
globalIdUsed: true,
});

Combine and divide workflows

The concept of grouping in the telecom domain network allows high-cardinality assets like fibers and ports to be represented efficiently. Editing workflows may require dividing grouped objects into smaller ranges or combining compatible ranges into a larger grouped object.

The FeatureService.applyEdits payload supports these operations with:

Unlike standard feature split/merge edits, which are geometry-driven edits on features, divide/combine workflows operate on grouped telecom objects and their unit identifier ranges. Use divide/combine when you need to repartition or consolidate logical unit capacity while preserving telecom grouping rules.

Example: divide grouped objects

Use UnitIdentifierManager.generateDivideNetworkElements to produce applyEdits payloads for dividing grouped objects.

In this example, the object with GUID 931A is divided into three grouped objects, with 2, 4, and 6 units respectively.

const dividePayload = unitIdentifierManager.generateDivideNetworkElements(
new NetworkElement({
globalId: "{931A1CD0-BF9B-43B1-B4B9-5BE4157DAA96}",
networkSourceId: 27,
}),
[2, 3, 4],
);
const divideResult = await featureService.applyEdits([dividePayload]);

See DivideGroupedObjectResult for the shape of divideResult.

Example: combine grouped objects

Use UnitIdentifierManager.generateCombineNetworkElements to produce applyEdits payloads for combining grouped objects.

const combinePayload = unitIdentifierManager.generateCombineNetworkElements([
new NetworkElement({
globalId: "{C5D8A7B2-1E4F-4C96-9B23-5F1A8D3E7C64}",
networkSourceId: 27,
}),
new NetworkElement({
globalId: "{D6E9C4F1-3A5B-4D7E-8C92-4B7D1E2F9A33}",
networkSourceId: 27,
}),
]);
const combineResult = await featureService.applyEdits([combinePayload]);

See CombineGroupedObjectResult for the shape of combineResult.