I have an issue when trying to create features with z-values for sewers using the Utility Network. In this case, I am trying to create sanitary sewer manholes from the Structure Junction > Vault group. I set the elevation in the elevation group of the edit tab. If I put in a whole number (e.g. 620), it will create the feature without problems. It will also take 1/4 values (e.g. 620.25, 620.5, 620.75). When I try to set it to a number with any other decimal value (e.g. 620.37), I get an error:
Failed to create Manhole.
Invalid Geometry [
Rule name: StructureJunction - Manhole
Elevation Attributes,
Triggering event: Insert,
Class name: StructureJunction
I have checked the Attribute Rules for Structure Junctions and the only apparent rule for the z-value is Round z to 2 sigfigs. I also did not have this issue with the stormwater manholes of the structure junction group, which use the same data type (double) for elevation.
Solved! Go to Solution.
It appears to be a change in the Text representation of a geometry. The M used to be reported as NaN and we were filtering these as the Point constructor fails with a NaN M. The M is not being returned as Null, so I had to extend the filter logic to also pop null Ms.
Line 71 is the only change in case you just want to add that to your code.
// Assigned To: StructureJunction
// Type: Calculation
// Name: StructureJunction - Manhole Elevation Attributes
// Description: Set rim elevation, invert elevation, and depth on Sewer Storm Vault - Manhole from the Z value. Update Manhole Channel content feature z value if needed.
// Subtypes: Sewer Storm Vault
// Field:
// Trigger: Insert, Update
// Exclude From Client: True
// Disable: False
// Related Rules: Some rules rely on additional rules for execution. If this rule works in conjunction with another, they are listed below:
// - None
// Duplicated in: This rule may be implemented on other classes, they are listed here to aid you in adjusting those rules when a code change is required.
// - None
// ************* User Variables *************
// This section has the functions and variables that need to be adjusted based on your implementation
// Limit the rule to valid asset types
// ** Implementation Note: Instead of recreating this rule for each asset type, this rules uses a list of domains and exits if not valid
var asset_type = $feature.assettype;
var valid_asset_types = [9];
// The rim elevation, invert elevation and depth field names
var rimelev_fld = "rimelev";
var invertelev_fld = "invertelev";
var depth_fld = "depth";
var depth = $feature.depth;
// The class names of the Manhole Channels
// ** Implementation Note: These are just the class/table name and should not be fully qualified.
var device_class = "SewerDevice";
// Sewer Device - Manhole Channel sql clause
// ** Implementation Note: Sql expression used to find content Manhole Channels to update z value with Invert Elevation value
var manhole_chan_sql = 'AssetGroup in (32) and AssetType in (301, 302)';
// ************* End User Variables Section *************
// ************* Functions *************
// monikerize FeatureSetByName function
var get_features_switch_yard = FeatureSetByName;
function get_content_manhole_chan() {
// find Manhole Channel globalid that is content of $feature
var associations = FeatureSetByAssociation($feature, 'content');
var filtered = Filter(associations, "className = @device_class");
var associated_ids = [];
for (var row in filtered) {
push(associated_ids, row.globalId)
}
if (Count(associated_ids) < 1) return null;
var device_fs = get_features_switch_yard($datastore, device_class, ["globalid", "assetgroup", 'assettype'], true);
var content_man_chan = First(Filter(device_fs, "GLOBALID in @associated_ids and " + manhole_chan_sql));
return iif(IsEmpty(content_man_chan), null, content_man_chan)
}
function update_geom(geo, new_z) {
// Set z on point to new value
var geo_dict = Dictionary(Text(geo));
geo_dict['z'] = new_z;
return Point(drop_nans(geo_dict))
}
function drop_nans(dict_with_nans) {
// drop any keys with a value of NaN
var new_dict = {};
for (var k in dict_with_nans) {
if (!IsNan(dict_with_nans[k]) && dict_with_nans[k] !=null ) {
new_dict[k] = dict_with_nans[k]
}
}
return new_dict
}
// ************* End Functions Section ******************
// Limit the rule to valid asset types
if (!Includes(valid_asset_types, asset_type)) {
return;
}
// Round z to 2 sigfigs
var geo_z = Geometry($feature).z;
geo_z = Round(geo_z, 2);
// build payload to update rim elevation, invert elevation, depth
if (IsEmpty(depth)) depth = 0;
var res = {"attributes": Dictionary(rimelev_fld, geo_z, invertelev_fld, geo_z - depth, depth_fld, depth)};
var ret = {"result": res};
// Get manhole channel and update z to invertelev
var man_chan = get_content_manhole_chan();
if (!IsEmpty(man_chan)) {
// if invertelev of Manhole does not match Z of Manhole Channel, update the Manhole Channel
if (Geometry(man_chan).z != res["attributes"][invertelev_fld]){
var update_manhole_chan = {
"globalid": man_chan.globalid,
'geometry': update_geom(Geometry(man_chan), res["attributes"][invertelev_fld])
};
var edit_payload = [{
'className': device_class,
'updates': [update_manhole_chan]
}];
ret["edit"] = edit_payload
}
}
return ret;
It appears to be a change in the Text representation of a geometry. The M used to be reported as NaN and we were filtering these as the Point constructor fails with a NaN M. The M is not being returned as Null, so I had to extend the filter logic to also pop null Ms.
Line 71 is the only change in case you just want to add that to your code.
// Assigned To: StructureJunction
// Type: Calculation
// Name: StructureJunction - Manhole Elevation Attributes
// Description: Set rim elevation, invert elevation, and depth on Sewer Storm Vault - Manhole from the Z value. Update Manhole Channel content feature z value if needed.
// Subtypes: Sewer Storm Vault
// Field:
// Trigger: Insert, Update
// Exclude From Client: True
// Disable: False
// Related Rules: Some rules rely on additional rules for execution. If this rule works in conjunction with another, they are listed below:
// - None
// Duplicated in: This rule may be implemented on other classes, they are listed here to aid you in adjusting those rules when a code change is required.
// - None
// ************* User Variables *************
// This section has the functions and variables that need to be adjusted based on your implementation
// Limit the rule to valid asset types
// ** Implementation Note: Instead of recreating this rule for each asset type, this rules uses a list of domains and exits if not valid
var asset_type = $feature.assettype;
var valid_asset_types = [9];
// The rim elevation, invert elevation and depth field names
var rimelev_fld = "rimelev";
var invertelev_fld = "invertelev";
var depth_fld = "depth";
var depth = $feature.depth;
// The class names of the Manhole Channels
// ** Implementation Note: These are just the class/table name and should not be fully qualified.
var device_class = "SewerDevice";
// Sewer Device - Manhole Channel sql clause
// ** Implementation Note: Sql expression used to find content Manhole Channels to update z value with Invert Elevation value
var manhole_chan_sql = 'AssetGroup in (32) and AssetType in (301, 302)';
// ************* End User Variables Section *************
// ************* Functions *************
// monikerize FeatureSetByName function
var get_features_switch_yard = FeatureSetByName;
function get_content_manhole_chan() {
// find Manhole Channel globalid that is content of $feature
var associations = FeatureSetByAssociation($feature, 'content');
var filtered = Filter(associations, "className = @device_class");
var associated_ids = [];
for (var row in filtered) {
push(associated_ids, row.globalId)
}
if (Count(associated_ids) < 1) return null;
var device_fs = get_features_switch_yard($datastore, device_class, ["globalid", "assetgroup", 'assettype'], true);
var content_man_chan = First(Filter(device_fs, "GLOBALID in @associated_ids and " + manhole_chan_sql));
return iif(IsEmpty(content_man_chan), null, content_man_chan)
}
function update_geom(geo, new_z) {
// Set z on point to new value
var geo_dict = Dictionary(Text(geo));
geo_dict['z'] = new_z;
return Point(drop_nans(geo_dict))
}
function drop_nans(dict_with_nans) {
// drop any keys with a value of NaN
var new_dict = {};
for (var k in dict_with_nans) {
if (!IsNan(dict_with_nans[k]) && dict_with_nans[k] !=null ) {
new_dict[k] = dict_with_nans[k]
}
}
return new_dict
}
// ************* End Functions Section ******************
// Limit the rule to valid asset types
if (!Includes(valid_asset_types, asset_type)) {
return;
}
// Round z to 2 sigfigs
var geo_z = Geometry($feature).z;
geo_z = Round(geo_z, 2);
// build payload to update rim elevation, invert elevation, depth
if (IsEmpty(depth)) depth = 0;
var res = {"attributes": Dictionary(rimelev_fld, geo_z, invertelev_fld, geo_z - depth, depth_fld, depth)};
var ret = {"result": res};
// Get manhole channel and update z to invertelev
var man_chan = get_content_manhole_chan();
if (!IsEmpty(man_chan)) {
// if invertelev of Manhole does not match Z of Manhole Channel, update the Manhole Channel
if (Geometry(man_chan).z != res["attributes"][invertelev_fld]){
var update_manhole_chan = {
"globalid": man_chan.globalid,
'geometry': update_geom(Geometry(man_chan), res["attributes"][invertelev_fld])
};
var edit_payload = [{
'className': device_class,
'updates': [update_manhole_chan]
}];
ret["edit"] = edit_payload
}
}
return ret;
Thank you! I made this change and it has fixed the issue.