UtilityNetwork.GetFeaturesForElementsAsync not returning all features

188
10
08-12-2020 12:32 PM
JoeHershman
MVP Regular Contributor

I am running a network trace (Find Connected) which does seem to return all the network elements.  However, when I try to get the features using UtilityNetwork.GetFeaturesForElementsAsync Method I do not get anywhere close to the number of features as their are elements.  For instance with the Gas Pipe I get 5856 elements returned.  Calling GetFeaturesForElementsAsync I get 14 features returned.

I am using the code from the sample and don't see any settings I could change to impact the conversion

var elements = traceResult.Elements.Where(e => e.NetworkSource.Name == featureLayer.FeatureTable.TableName).ToList();
if ( !elements.Any() ) continue;

features = (await _utilityNetwork.GetFeaturesForElementsAsync(elements)).ToList();

Thoughts on what I am doing wrong here?

Thanks

-Joe

Reply
0 Kudos
10 Replies
JoeHershman
MVP Regular Contributor

So I have found that it would seem when I create a UtilityNetwork using UtilityNetwork.CreateAsync Method (Uri, Credential) I am getting correct results when using UtilityNetwork.GetFeaturesForElementsAsync Method, however, when I create it using UtilityNetwork.CreateAsync Method (Uri, Map) method I am getting the invalid results.

This does not make sense to me as the Map contains most layers, and it is the same Map I am using to view the to set the start location and view the results. 

Reply
0 Kudos
JenniferNery
Esri Regular Contributor

What version of ArcGIS Runtime SDK for .NET are you using? I believe we fixed a bug in 100.6 which is as you describe GetFeaturesForElementsAsync does not return all features.

A workaround for that release is to select or query by object id

var query = new QueryParameters();
foreach (var id in (from f in elementGroup select f.ObjectId).Distinct())
query.ObjectIds.Add(id);
layer.SelectFeaturesAsync(query, SelectionMode.New);

Can you clarify what invalid results you get? 

when I create it using UtilityNetwork.CreateAsync Method (Uri, Map) method I am getting the invalid results.

If UtilityNetwork was created with a map, the UtilityNetwork.Definition.NetworkSources which have a FeatureTable property will be the same instances in your map.

If you created UtilityNetwork first and you wanted to build your map from it, you can also do

foreach (var ns in _un.Definition.NetworkSources)
{
if (ns.SourceUsageType == UtilityNetworkSourceUsageType.SubnetLine)
continue;
map.OperationalLayers.Insert(0, new FeatureLayer(ns.FeatureTable));
}
Reply
0 Kudos
JoeHershman
MVP Regular Contributor

Using 100.8

Invalid results as I described, it returns 14 features when there are 5856 elements.  The WebMap I use has Pipes, Junctions, and Devices.  So all the main FeatureTables are represented.

The idea of manually building out the map seems a poor workflow.  How would I manage layer order, visibility, etc.  Would be tough to make anything more the a very simple map using that approach.

Something else I notice, it is terribly slow to open the WebMap.  It takes about 20 seconds for the original drawing of the map.  And we are zoomed at a level where only distribution pipes are visible

Startup view:

Reply
0 Kudos
JenniferNery
Esri Regular Contributor

The 5856 elements may not be distinct and may also be belonging to other layers.

Do you do something like this? 

var result = await _un.TraceAsync(_parameters);
if (result.FirstOrDefault() is UtilityElementTraceResult elementResult)
{
if (elementResult.Warnings.Count > 0)
MessageBox.Show(string.Join("\n", elementResult.Warnings), "Trace Result Warnings", MessageBoxButton.OK);

foreach (var layer in MyMapView.Map.OperationalLayers.OfType<FeatureLayer>())
{
var elements = elementResult.Elements.Where(el => el.NetworkSource.FeatureTable == layer.FeatureTable);
var features = await _un.GetFeaturesForElementsAsync(elements);
layer.SelectFeatures(features);
}
}

I added this check and the counts seem to match.

var distinctIds = (from f in elements select f.ObjectId).Distinct();
System.Diagnostics.Debug.WriteLine($"Unique Elements `{distinctIds.Count()}` vs Features From Elements `{features.Count()}`");
Reply
0 Kudos
JenniferNery
Esri Regular Contributor

I think I have a reproducer now. However, the difference in numbers is not as big as yours and does not change if I create UtilityNetwork with or without a map.

Electric Distribution Line Unique Elements `4052` vs Features From Elements `4066` (more features than elements)
Electric Distribution Junction Unique Elements `3334` vs Features From Elements `3334` 
Electric Distribution Device Unique Elements `5045` vs Features From Elements `5425` (more features than elements)

var FeatureServerUrl = "https://sampleserver7.arcgisonline.com/arcgis/rest/services/UtilityNetwork/NapervilleElectric/FeatureServer";

var map = new Map(Basemap.CreateDarkGrayCanvasVector());
map.InitialViewpoint = new Viewpoint(InitialExtent);
map.OperationalLayers.Add(new FeatureLayer(new Uri($"{FeatureServiceUrl}/115")));
map.OperationalLayers.Add(new FeatureLayer(new Uri($"{FeatureServiceUrl}/110")));
map.OperationalLayers.Add(new FeatureLayer(new Uri($"{FeatureServiceUrl}/100")));
await map.LoadAsync();

_un = await UtilityNetwork.CreateAsync(new Uri(FeatureServiceUrl), map);

var networkSource = _un.Definition.GetNetworkSource("Electric Distribution Device");
var assetGroup = networkSource?.GetAssetGroup("Circuit Breaker");
var assetType = assetGroup?.GetAssetType("Three Phase");
var terminal = assetType?.TerminalConfiguration?.Terminals?.FirstOrDefault(t => t.Name == "Source");
var globalId = Guid.Parse("{B8493B23-B40B-4E3E-8C4B-0DD83C28D2CC}");
var element = _un.CreateElement(assetType, globalId, terminal);

var parameters = new UtilityTraceParameters(UtilityTraceType.Connected, new[] { element });

var result = await _un.TraceAsync(parameters);

if (result.FirstOrDefault() is UtilityElementTraceResult elementResult)
{
if (elementResult.Warnings.Count > 0)
MessageBox.Show(string.Join("\n", elementResult.Warnings), "Trace Result Warnings", MessageBoxButton.OK);

foreach (var layer in MyMapView.Map.OperationalLayers.OfType<FeatureLayer>())
{
var elements = elementResult.Elements.Where(el => el.NetworkSource.FeatureTable == layer.FeatureTable);
var features = await _un.GetFeaturesForElementsAsync(elements);
var distinctIds = (from f in elements select f.ObjectId).Distinct();
System.Diagnostics.Debug.WriteLine($"{layer.FeatureTable.DisplayName}\t:\t Unique Elements `{distinctIds.Count()}` vs Features From Elements `{features.Count()}`");
layer.SelectFeatures(features);
}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

In your case, could there be layers that did not load or required credential? You can add the following to troubleshoot.

// Check if any layer failed
MyMapView.LayerViewStateChanged += (s, e) =>
{
var error = e.LayerViewState.Error ?? e.Layer.LoadError;
if (error != null)
System.Diagnostics.Debug.WriteLine($"{e.Layer.Name} had an error {error.Message}");
};

// Check if any layer required credential
AuthenticationManager.Current.ChallengeHandler = new ChallengeHandler(async(info) =>
{
return await AuthenticationManager.Current.GenerateCredentialAsync(info.ServiceUri, "<username>", "<password>");
});‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
Reply
0 Kudos
JoeHershman
MVP Regular Contributor

If you look at my code, I have already filtered the elements by FeatureTable, 

var elements = traceResult.Elements.Where(e => e.NetworkSource.Name == featureLayer.FeatureTable.TableName).ToList();

The total number of elements returned in the trace results is far greater.  After running the trace using

var traceResults = await _utilityNetwork.TraceAsync(parameters);

I get 11624 elements whether I use the map to Create the UtilityNetwork or use credentials to Create the UtilityNetwork

But I am getting completely different results from GetFeaturesForElementsAsync based on how I initialize the UtilityNetwork.  I have tested using the exact same start location of the FindConnected trace.  Using the map, I do not get the complete result set as Features, using Credentials I do. 

Reply
0 Kudos
RichRuh
Esri Regular Contributor

Hi Joe!

Hope you and your family are doing well.

We've had a couple of developers looking at this, and cannot find a way to reproduce it.  Is there a chance you can use Fiddler, and send us the input/output from the calls to GetFeaturesForElementsAsync for the two different calls to CreateAsync()?  Obviously, if your .NET app runs on Windows as well as the iPad this is straightforward.  Otherwise you might need to follow these directions here.  It's probably easiest to just e-mail the Fiddler files to rruh@esri.com.

If this isn't something you can easily do, let me know and we'll see if we can find another approach.

Thanks,

--Rich

Reply
0 Kudos
JoeHershman
MVP Regular Contributor

Rich,

I tried to do with Fiddler both from my machine using an iOS simulator and the approach to capture incoming traffic.  Not able to get around ssl/cert issues.  We are setup on AWS using a certificate through AWS and they operate in a kind of bizarre manner compared to how one normally sets up certificates (imo), and cannot see how to work around it.

-Joe

Reply
0 Kudos
JenniferNery
Esri Regular Contributor

Thank you, Joe for the valuable information you sent us. The issue appears to be the definition expression that is on some of the layers.  These expressions are loaded from the webmap, and are filtering out the results returned from GetFeaturesForElementsAsync().  We’ve logged an issue to ignore the expression during this function call.

 

It looks like the definition expressions are being used to create multiple layers, one for each AssetGroup.  If you have Pro 2.6 and Enterprise 10.8.1, you can create a map in Pro that contains subtype group layers and publish it to a webmap.  This webmap can be consumed using Runtime 100.9, which just shipped today.

 

We suggest using a single SubtypeGroupLayer instead for each server endpoint (i.e. Gas Device/Line/Junction) when you publish your webmap using ArcGIS Pro.  This will improve performance, and eliminate the need for using the definition expression.  The Runtime equivalent of this layer is SubtypeFeatureLayer. Since it is a sub-class of FeatureLayer, there’s minimal to no change in code necessary. If you are identifying features from this layer, the result will be in SublayerResults.

 

var feature = result.FirstOrDefault(r => r.SublayerResults.Any(sr => sr.GeoElements.Any(g => g is ArcGISFeature)))
?.SublayerResults?.FirstOrDefault()?.GeoElements?.FirstOrDefault() as ArcGISFeature;

 

Reply
0 Kudos