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;
Solved! Go to Solution.
Take a look at the notes here, I requires two rules, one on the line and one on the point
Take a look at the notes here, I requires two rules, one on the line and one on the point
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
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;
You mean after var is_un_controlled = false?
oh okay so i have did only the rule for the device and not the line