Select to view content in your preferred language

Help needed with Attribute rule creation in utility network for pipe splitting

331
6
Jump to solution
03-27-2025 05:53 AM
angiexx1
Emerging Contributor

i need to split the water line with a asset group of service line and asset type of residential line when a water device of asset group of service valve, asset type service is snapped on the water line, i build the attribute rule, there is no error showing up but also the rule is not executed.

 

// Exit Early Configuration
Expects($feature, 'operable', 'assetgroup', 'assettype');
var exit_early_values = ['No'];
var remove_fields_from_new_feature = ['GLOBALID', 'OBJECTID', 'CREATIONDATE', 'CREATOR', 'LASTUPDATE', 'UPDATEDBY'];
var line_class_name = "WaterLine";
var point_class_name = "Water Device";
var line_fs = FeatureSetByName($datastore, "WaterLine", ['*'], true);

// Filter WaterLine to only include Service Line (asset group = 2) and Residential (asset type = 11)
Console("Filtering WaterLine features...");
var filtered_line_fs = Filter(line_fs, "assetgroup = 2 AND assettype = 11");
var filtered_count = Count(filtered_line_fs);
Console("Number of filtered WaterLine features: " + Text(filtered_count));
if (filtered_count == 0) {
    Console("No WaterLine features match assetgroup = 2 AND assettype = 11");
    var i = 0;
    for (var f in line_fs) {
        if (i >= 3) break;
        Console("WaterLine " + Text(i) + " assetgroup: " + Text(f.assetgroup) + ", assettype: " + Text(f.assettype));
        i++;
    }
    return;
}

// Use a larger buffer to account for snapping tolerances
var buffer_pnt_distance = 1.0; // Reduced to 1 meter for precision
var point_on_line_tol = 10.0; // Increased to 10 meters to allow for snapping issues
var is_un_controlled = false;

// Helper Functions
function get_fields_by_type(feat, convert_string, param, value) {
    var fields = Schema(feat).fields;
    var return_fields = [];
    var func = Decode(Lower(convert_string), "lower", Lower, "upper", Upper, Text);
    for (var f in fields) {
        if (fields[f][param] == value) {
            var fld_name = fields[f].name;
            if (!IsEmpty(convert_string)) {
                fld_name = func(fld_name);
            }
            Push(return_fields, fld_name);
        }
    }
    return return_fields;
}

function set_date_type(feat, dict) {
    var dt_keys = get_fields_by_type(feat, 'upper', 'type', 'esriFieldTypeDate');
    for (var k in dict) {
        if (IndexOf(dt_keys, Upper(k)) == -1) {
            continue;
        }
        dict[k] = Date(dict[k]);
    }
    return dict;
}

function pop_keys(dict, keys) {
    var new_dict = {};
    for (var k in dict) {
        if (IndexOf(keys, Upper(k)) == -1) {
            new_dict[k] = dict[k];
        }
    }
    return new_dict;
}

function parse_json_number(json_str, key) {
    var key_start = Find('"' + key + '":', json_str);
    if (key_start == -1) {
        Console("Failed to find key '" + key + "' in JSON string: " + json_str);
        return null;
    }

    var value_start = key_start + Length('"' + key + '":');
    var value_end = Find(',', json_str, value_start);
    if (value_end == -1) {
        value_end = Find('}', json_str, value_start);
    }
    if (value_end == -1) {
        Console("Failed to find end of value for key '" + key + "' in JSON string: " + json_str);
        return null;
    }

    var value_str = Mid(json_str, value_start, value_end - value_start);
    var value = Number(Trim(value_str));
    if (TypeOf(value) != 'Number' || IsNan(value)) {
        Console("Parsed value for key '" + key + "' is not a number: " + value_str);
        return null;
    }
    return value;
}

function parse_json_array(json_str, key) {
    var key_start = Find('"' + key + '":', json_str);
    if (key_start == -1) {
        Console("Failed to find key '" + key + "' in JSON string: " + json_str);
        return null;
    }

    var value_start = key_start + Length('"' + key + '":');
    var bracket_start = Find('[', json_str, value_start);
    if (bracket_start == -1) {
        Console("Failed to find array start for key '" + key + "' in JSON string: " + json_str);
        return null;
    }

    // Find the matching closing bracket
    var bracket_count = 1;
    var pos = bracket_start + 1;
    var value_end = -1;
    while (pos < Length(json_str) && bracket_count > 0) {
        var char = Mid(json_str, pos, 1);
        if (char == '[') {
            bracket_count++;
        } else if (char == ']') {
            bracket_count--;
            if (bracket_count == 0) {
                value_end = pos + 1;
                break;
            }
        }
        pos++;
    }

    if (value_end == -1) {
        Console("Failed to find array end for key '" + key + "' in JSON string: " + json_str);
        return null;
    }

    var array_str = Mid(json_str, bracket_start, value_end - bracket_start);
    return array_str;
}

function parse_paths_array(array_str) {
    // Remove outer brackets
    array_str = Trim(array_str);
    if (Left(array_str, 1) != '[' || Right(array_str, 1) != ']') {
        Console("Invalid array string format: " + array_str);
        return null;
    }
    array_str = Mid(array_str, 1, Length(array_str) - 2);

    // Split into individual paths
    var paths = [];
    var bracket_count = 0;
    var start_pos = 0;
    for (var i = 0; i < Length(array_str); i++) {
        var char = Mid(array_str, i, 1);
        if (char == '[') {
            bracket_count++;
        } else if (char == ']') {
            bracket_count--;
            if (bracket_count == 0) {
                var path_str = Mid(array_str, start_pos, i - start_pos + 1);
                var path = parse_path(path_str);
                if (IsEmpty(path)) {
                    Console("Failed to parse path: " + path_str);
                    return null;
                }
                Push(paths, path);
                start_pos = i + 2; // Skip the comma and space
            }
        }
    }

    return paths;
}

function parse_path(path_str) {
    // Remove outer brackets
    path_str = Trim(path_str);
    if (Left(path_str, 1) != '[' || Right(path_str, 1) != ']') {
        Console("Invalid path string format: " + path_str);
        return null;
    }
    path_str = Mid(path_str, 1, Length(path_str) - 2);

    // Split into individual vertices
    var vertices = [];
    var bracket_count = 0;
    var start_pos = 0;
    for (var i = 0; i < Length(path_str); i++) {
        var char = Mid(path_str, i, 1);
        if (char == '[') {
            bracket_count++;
        } else if (char == ']') {
            bracket_count--;
            if (bracket_count == 0) {
                var vertex_str = Mid(path_str, start_pos, i - start_pos + 1);
                var vertex = parse_vertex(vertex_str);
                if (IsEmpty(vertex)) {
                    Console("Failed to parse vertex: " + vertex_str);
                    return null;
                }
                Push(vertices, vertex);
                start_pos = i + 2; // Skip the comma and space
            }
        }
    }

    return vertices;
}

function parse_vertex(vertex_str) {
    // Remove outer brackets
    vertex_str = Trim(vertex_str);
    if (Left(vertex_str, 1) != '[' || Right(vertex_str, 1) != ']') {
        Console("Invalid vertex string format: " + vertex_str);
        return null;
    }
    vertex_str = Mid(vertex_str, 1, Length(vertex_str) - 2);

    // Split into x and y coordinates
    var coords = Split(vertex_str, ',');
    if (Count(coords) < 2) {
        Console("Vertex does not contain enough coordinates: " + vertex_str);
        return null;
    }

    var x = Number(Trim(coords[0]));
    var y = Number(Trim(coords[1]));
    if (TypeOf(x) != 'Number' || TypeOf(y) != 'Number' || IsNan(x) || IsNan(y)) {
        Console("Vertex coordinates are not numbers: x=" + Text(x) + ", y=" + Text(y));
        return null;
    }

    return [x, y];
}

function cut_line_at_point(line_geometry, point_geometry, exit_when_not_at_vertex) {
    Console("Entering cut_line_at_point function");

    if (IsEmpty(line_geometry)) {
        Console("Line geometry is empty");
        return {'success': false};
    }
    if (IsEmpty(point_geometry)) {
        Console("Point geometry is empty");
        return {'success': false};
    }

    var line_sref = line_geometry.spatialReference.wkid;
    var point_sref = point_geometry.spatialReference.wkid;
    Console("Line spatial reference: " + Text(line_sref));
    Console("Point spatial reference: " + Text(point_sref));

    // Check if spatial references match
    if (line_sref != point_sref) {
        Console("Spatial references do not match: line=" + Text(line_sref) + ", point=" + Text(point_sref));
        return {'success': false};
    }

    // Parse line geometry manually
    var geom_text = Text(line_geometry);
    Console("Raw line geometry text: " + geom_text);

    if (IsEmpty(geom_text) || geom_text == "{}") {
        Console("Line geometry text is empty or invalid");
        return {'success': false};
    }

    var paths_str = parse_json_array(geom_text, 'paths');
    if (IsEmpty(paths_str)) {
        Console("Failed to parse paths from line geometry");
        return {'success': false};
    }

    var paths = parse_paths_array(paths_str);
    if (IsEmpty(paths)) {
        Console("Failed to parse paths array");
        return {'success': false};
    }

    var path_count = Count(paths);
    Console("Number of paths in line: " + Text(path_count));
    if (path_count == 0) {
        Console("No paths found in line geometry");
        return {'success': false};
    }

    // Parse point geometry manually
    var point_text = Text(point_geometry);
    Console("Raw point geometry text: " + point_text);

    if (IsEmpty(point_text) || point_text == "{}") {
        Console("Point geometry text is empty or invalid");
        return {'success': false};
    }

    var point_x = parse_json_number(point_text, 'x');
    var point_y = parse_json_number(point_text, 'y');
    if (IsEmpty(point_x) || IsEmpty(point_y)) {
        Console("Failed to parse x, y coordinates from point geometry");
        return {'success': false};
    }

    // Additional validation: Ensure x and y are not null or NaN
    if (IsNan(point_x) || IsNan(point_y)) {
        Console("Point x, y coordinates are invalid: x=" + Text(point_x) + ", y=" + Text(point_y));
        return {'success': false};
    }

    Console("Water Device point: x=" + Text(point_x) + ", y=" + Text(point_y));

    // Find the closest vertex on the WaterLine to the Water Device point
    var closest_path_index = -1;
    var closest_vertex_index = -1;
    var min_distance = Number.MAX_VALUE;

    for (var p in paths) {
        var path = paths[p];
        for (var v in path) {
            var vertex = path[v];
            var dx = vertex[0] - point_x;
            var dy = vertex[1] - point_y;
            var distance = Sqrt(dx * dx + dy * dy);
            if (distance < min_distance) {
                min_distance = distance;
                closest_path_index = Number(p);
                closest_vertex_index = Number(v);
            }
        }
    }

    if (closest_path_index == -1 || closest_vertex_index == -1) {
        Console("Could not find closest vertex to Water Device point");
        return {'success': false};
    }

    Console("Closest vertex at pathIndex: " + Text(closest_path_index) + ", vertexIndex: " + Text(closest_vertex_index));
    Console("Distance to closest vertex: " + Text(min_distance) + " meters");

    if (min_distance > point_on_line_tol) {
        Console("Water Device point is too far from line (distance: " + Text(min_distance) + " meters)");
        return {'success': false};
    }

    // Check if the point is at the start or end of the line
    var first_path = paths[0];
    var last_path = paths[path_count - 1];
    var first_vertex = first_path[0];
    var last_vertex = last_path[Count(last_path) - 1];
    var is_start = (closest_path_index == 0 && closest_vertex_index == 0);
    var is_end = (closest_path_index == (path_count - 1) && closest_vertex_index == (Count(last_path) - 1));
    if (is_start || is_end) {
        Console("Point is at the start or end of the line, cannot split");
        return {'success': false};
    }

    // Split the paths at the closest vertex
    Console("Attempting to split the line at the closest vertex");
    var split_coord = [point_x, point_y]; // Use the Water Device point coordinates for the split
    var new_path_1 = Slice(paths, 0, closest_path_index + 1);
    var new_path_2 = Slice(paths, closest_path_index);

    var new_path_1_count = Count(new_path_1);
    var last_seg_1 = new_path_1[new_path_1_count - 1];
    var new_seg_1 = Slice(last_seg_1, 0, closest_vertex_index + 1);
    Push(new_seg_1, split_coord);
    new_path_1[new_path_1_count - 1] = new_seg_1;

    var first_seg_2 = new_path_2[0];
    var new_seg_2 = Slice(first_seg_2, closest_vertex_index);
    Insert(new_seg_2, 0, split_coord);
    new_path_2[0] = new_seg_2;

    Console("Split successful, returning new paths");
    return {
        'success': true,
        'delete_original': true,
        'new_lines': [new_path_1, new_path_2]
    };
}

function check_exit_early(feat) {
    Console("Entering check_exit_early function");

    var asset_group = Text(feat.assetgroup);
    var asset_type = Text(feat.assettype);
    Console("Water Device assetgroup: " + asset_group);
    Console("Water Device assettype: " + asset_type);

    var is_service_valve = feat.assetgroup == 6;
    var is_service_type = feat.assettype == 176;
    if (!is_service_valve || !is_service_type) {
        Console("Water Device is not a Service Valve (6) with Service type (176), exiting");
        return true;
    }

    var operable_value = Text(feat.operable);
    Console("Operable value: " + operable_value);
    if (IsEmpty(feat.operable)) {
        Console("Operable is empty, proceeding");
        return false;
    }

    var should_exit = Includes(exit_early_values, feat.operable);
    Console("Should exit based on operable: " + Text(should_exit));
    return should_exit;
}

// Main Logic
var feature_global_id = Text($feature.globalID);
Console("Starting rule execution for Water Device: " + feature_global_id);

// Check if we should exit early
var should_exit = check_exit_early($feature);
if (should_exit) {
    Console("Exiting early due to operable condition or incorrect asset group/type");
    return;
}

// Validate $feature geometry
Console("Validating $feature geometry...");
var feature_geometry = Geometry($feature);
if (IsEmpty(feature_geometry)) {
    Console("Feature geometry is empty or invalid");
    return;
}

// Additional validation: Check geometry type and spatial reference
var geom_type = TypeOf(feature_geometry);
Console("Feature geometry type: " + geom_type);
if (geom_type != "Point") {
    Console("Feature geometry is not a Point, type: " + geom_type);
    return;
}

var feature_sref = feature_geometry.spatialReference.wkid;
Console("Feature spatial reference: " + Text(feature_sref));
if (IsEmpty(feature_sref)) {
    Console("Feature spatial reference is invalid");
    return;
}

Console("Feature geometry validated successfully");

// Validate filtered_line_fs geometries and collect valid lines
Console("Validating WaterLine geometries in filtered_line_fs...");
var valid_lines = [];
for (var line in filtered_line_fs) {
    var line_geom = Geometry(line);
    if (IsEmpty(line_geom)) {
        Console("WaterLine " + Text(line.globalID) + " has empty or invalid geometry");
        continue;
    }
    var line_geom_type = TypeOf(line_geom);
    if (line_geom_type != "Polyline") {
        Console("WaterLine " + Text(line.globalID) + " geometry is not a Polyline, type: " + line_geom_type);
        continue;
    }
    var line_sref = line_geom.spatialReference.wkid;
    if (IsEmpty(line_sref)) {
        Console("WaterLine " + Text(line.globalID) + " has invalid spatial reference");
        continue;
    }
    if (line_sref != feature_sref) {
        Console("WaterLine " + Text(line.globalID) + " spatial reference does not match feature: " + Text(line_sref));
        continue;
    }
    Push(valid_lines, line);
}

var valid_line_count = Count(valid_lines);
Console("Number of valid WaterLine features: " + Text(valid_line_count));
if (valid_line_count == 0) {
    Console("No valid WaterLine features found after validation");
    return;
}

// Find intersecting lines with a small buffer
Console("Buffering Water Device point with distance: " + Text(buffer_pnt_distance) + " meters...");
var buffered_point = Buffer(feature_geometry, buffer_pnt_distance);
if (IsEmpty(buffered_point)) {
    Console("Failed to buffer Water Device point");
    return;
}

Console("Finding intersecting lines...");
var intersecting_lines = [];
for (var line in valid_lines) {
    var line_geom = Geometry(line);
    var intersects_result = Intersects(buffered_point, line_geom);
    if (intersects_result) {
        Console("Found intersecting line: " + Text(line.globalID));
        Push(intersecting_lines, line);
    }
}

var line_count = Count(intersecting_lines);
Console("Number of intersecting lines: " + Text(line_count));
if (line_count == 0) {
    Console("No intersecting lines found after buffering");
    return;
}

var in_point_geometry = feature_geometry;
var delete_features = [];
var new_features = [];

for (var line_feature in intersecting_lines) {
    var line_global_id = Text(line_feature.globalID);
    Console("Processing line: " + line_global_id);

    var line_geometry = Geometry(line_feature);
    if (IsEmpty(line_geometry)) {
        Console("Line geometry is empty or invalid for line: " + line_global_id);
        continue;
    }

    var split_result = cut_line_at_point(line_geometry, in_point_geometry, is_un_controlled);

    var split_success = split_result['success'];
    var new_lines = split_result['new_lines'];
    var new_line_count = Count(new_lines);
    Console("Split success: " + Text(split_success));
    Console("Number of new lines: " + Text(new_line_count));
    if (!split_success || new_line_count != 2) {
        Console("Split failed or invalid number of new lines");
        continue;
    }

    var new_geom_1 = new_lines[0];
    var new_geom_2 = new_lines[1];

    var geom_1_count = Count(new_geom_1);
    var new_geom_2_count = Count(new_geom_2);
    Console("New geom 1 vertex count: " + Text(geom_1_count));
    Console("New geom 2 vertex count: " + Text(new_geom_2_count));

    if (geom_1_count < 2 || new_geom_2_count < 2) {
        Console("New geometry has insufficient vertices");
        continue;
    }

    var line_spat_ref = line_geometry.spatialReference.wkid;

    var polyline_1 = Polyline({
        'paths': new_geom_1,
        'spatialReference': {'wkid': line_spat_ref}
    });

    var polyline_2 = Polyline({
        'paths': new_geom_2,
        'spatialReference': {'wkid': line_spat_ref}
    });

    // Construct the attribute dictionary directly
    var line_att = {
        'assettype': line_feature.assettype,
        'assetgroup': line_feature.assetgroup
    };

    if (HasKey(line_feature, 'diameter')) {
        line_att['diameter'] = line_feature.diameter;
    }
    if (HasKey(line_feature, 'lifecyclestatus')) {
        line_att['lifecyclestatus'] = line_feature.lifecyclestatus;
    }
    if (HasKey(line_feature, 'maintby')) {
        line_att['maintby'] = line_feature.maintby;
    }
    if (HasKey(line_feature, 'ownedby')) {
        line_att['ownedby'] = line_feature.ownedby;
    }

    var atts_to_remove = get_fields_by_type(line_feature, 'upper', 'editable', false);

    for (var i in remove_fields_from_new_feature) {
        var fld = Upper(remove_fields_from_new_feature[i]);
        var is_in_atts = IndexOf(atts_to_remove, fld);
        if (is_in_atts != -1) {
            continue;
        }
        Push(atts_to_remove, fld);
    }

    var popped_atts = pop_keys(line_att, atts_to_remove);
    line_att = set_date_type(line_feature, popped_atts);

    var delete_entry = {'globalID': line_feature.globalID};
    Push(delete_features, delete_entry);
    Console("Adding delete for original line: " + line_global_id);

    var new_feature_1 = {
        'geometry': polyline_1,
        'attributes': line_att
    };
    Push(new_features, new_feature_1);
    Console("Added new feature 1");

    var new_feature_2 = {
        'geometry': polyline_2,
        'attributes': line_att
    };
    Push(new_features, new_feature_2);
    Console("Added new feature 2");
}

var delete_count = Count(delete_features);
var new_count = Count(new_features);
Console("Number of features to delete: " + Text(delete_count));
Console("Number of new features: " + Text(new_count));
if (delete_count > 0 && new_count > 0) {
    var results = {};
    var edit_entry = {
        'className': line_class_name,
        'deletes': delete_features,
        'adds': new_features
    };
    results['edit'] = [edit_entry];
    Console("Returning edit dictionary");
    return results;
}

Console("No edits to return");
return;
0 Kudos
1 Solution

Accepted Solutions
MikeMillerGIS
Esri Frequent Contributor

Take a look at the notes here, I requires two rules, one on the line and one on the point

 

 

View solution in original post

0 Kudos
6 Replies
MikeMillerGIS
Esri Frequent Contributor

Take a look at the notes here, I requires two rules, one on the line and one on the point

 

 

0 Kudos
MikeMillerGIS
Esri Frequent Contributor
0 Kudos
angiexx1
Emerging Contributor

Hi Mike i tried that and it works well for the point and line splitting but when it comes to the UN its not properly working

0 Kudos
MikeMillerGIS
Esri Frequent Contributor

The markdown has a section titled very important. Which references the below variable, which is not set correctly in your code.

 

var is_un_controlled = false;

0 Kudos
angiexx1
Emerging Contributor

You mean after var is_un_controlled = false?

0 Kudos
angiexx1
Emerging Contributor

oh okay so i have did only the rule for the device and not the line

0 Kudos