Select to view content in your preferred language

Attribute rule to merge road centerline address ranges

352
8
Jump to solution
a month ago
BriannaF
Occasional Contributor

I've created an attribute rule to identify two road segments that have been merged using the Merge (Editing) tool and "merge" the address ranges together. In order for it to work successfully, the user must be sure to select the FIRST segment of the two being merged in terms of line direction to preserve attributes from in the Modify Features pane (the yellow highlighted segment in the screenshot below). The attribute rule fires on Delete, since a merge should consist of a Delete and an Update (when choosing to merge into an existing feature rather than a new feature). The rule successfully identifies another feature in the roads layer that has the same street name and whose end point is equal to the start point of the deleted feature - this should be the feature that is updated during the merge. I recognize this may not work perfectly in all scenarios but for now I'm just testing if possible to get this to work at all.

BriannaF_0-1761854503235.png

Both the deleted and updated feature from the merge are correctly identified within the attribute rule, and it successfully identifies the address ranges of both and performs calculations to ensure the ranges are compatible for merging and correctly sets the final "merged" range values (keeping the from left and from right ranges from the first/updated segment, and the to left and to right ranges from the second/deleted segment).

I then try to update the range fields in the first/updated segment using the Global ID. The rule runs successfully with no errors, but despite confirming that the final calculated ranges for the merge are correct using Console messages, the updated segment has the same address ranges as it originally did after the rule fires.

I tried to set up a test rule to run on Update to see if I could determine whether the Delete or Update happens first when using the Merge editing tool but couldn't get the Console messages to print in the Diagnostic Monitor log, although that may just be an issue with the content of the Arcade expression. I'm wondering if the Delete happens first so the feature to update as part of the merge is correctly updated, and then the Update happens so it somehow reverts to the original state from the merge.

Does anyone have any deeper insight into the inner workings of the Merge editing tool or suggestions on how this could potentially work?

@HusseinNasser2 I have sat in on a number of your attribute rule presentations at the Esri Dev & Tech Summit 2024 and 2025 as well as Esri UC 2025, as well as sat down with you for a bit at Dev & Tech Summit 2024 to get help with a similar attribute rule that could identify which 2 segments were just split and recalculate the ranges. This rule is sort of the opposite of that but I quickly realized it's a bit more complicated. I'm wondering if you might have any insight into this functionality and if you think it's possible to accomplish this merge functionality? Thanks in advance for any suggestions!

Below is the final portion of the Arcade expression for reference.

// The Arcade code leading up to this correctly identifies the 2 segments
// that are part of the merge, gets their attributes, and calculates
// whether the address ranges are compatible to be merged

// Compute final union ranges (deleted is SECOND, merged is FIRST)
var final_L_from = toNum(mergedFromL);
var final_L_to   = toNum(deletedToL);
var final_R_from = toNum(mergedFromR);
var final_R_to   = toNum(deletedToR);

// These console messages print out the correct final ranges
Console("Final L From: " + Text(final_L_from));
Console("Final L To: "   + Text(final_L_to));
Console("Final R From: " + Text(final_R_from));
Console("Final R To: "   + Text(final_R_to));

// This console message prints out the correct ID to be updated
Console("Merged Road ID to Update: " + Text(mergedRoad.GlobalID));

// Apply updates to the kept (merged) feature
return {
  'edit': [{
    'className': 'ROADS_StatePlane',
    'updates': [{
      'globalID': mergedRoad.GlobalID,
      'attributes': {
        'LFROM_Test': final_L_from,
        'LTO_Test':   final_L_to,
        'RFROM_Test': final_R_from,
        'RTO_Test':   final_R_to
      }
    }]
  }]
};

 

Here is a screenshot of the Console messages:

BriannaF_1-1761854702292.png

 

2 Solutions

Accepted Solutions
HusseinNasser2
Esri Contributor

I'm not sure if the user workflow allows it but I would suggest using the option to keep the original features  and let the attribute rules do the delete instead of the merge tool. This gives you more control as it turns the operation from a delete , update. To an update only and you will write the rule to trigger on update on geometry (use the triggering fields to set the rule to trigger only on the shape change)

on the update you will have the control to query the original features (since they won't be deleted) and copy any attribute you want off of it. And then later you can insert the logic to actually delete the original feature.

 

HusseinNasser2_0-1761868786004.png

 

My guess as to why you are losing your update is as you suspected the order of the operations causes your update to be lost/ not persisted. 

 

 

View solution in original post

0 Kudos
HusseinNasser2
Esri Contributor

I authored an example attribute rule to demonstrate this on merge. When the user selects two line features and wants to merge, the attribute rule will pick the largest line segment and copy the addresstext of that line feature to the new merged feature and then the rule will delete the old two segments. 

Hope this helps

 

 

//When merging with the option "new feature" the rule will be executed on insert, if we enabled keep original features we get access to the original rows , this way we can query the merged lines, pick the largest segment , take the address from it and copy it to the new merged line. after merging we issue a delete to delete the two original segements. 
var g = $feature.GlobalID
var fs = contains(filter($featureset, "globalid <> @g"), geometry($feature))
 
var biggerAddress = null;
var biggerLine = null;
var tobeDeleted = []

for (var g in fs) 
   { 
    push(tobeDeleted, {"globalId": g.globalid} )

    if (biggerLine == null){
      biggerLine = g;
      biggerAddress = g.addresstext;
    }
    
    if (length(g) >= length(biggerLine)){
      biggerLine = g;
      biggerAddress = g.addresstext;
    }
      
      
  }


if (biggerLine == null) return;

return {
  "result": {"attributes": {"addresstext": biggerAddress}},
  "edit": [{
    "className": "main.theline",
    "deletes": tobeDeleted
  }]
}

 

 

View solution in original post

8 Replies
RPGIS
by MVP Regular Contributor
MVP Regular Contributor

Hi @BriannaF,

So there are a couple of ways around it so here are some options.

  1. Set the rule to filter the featureset to filter based on a common value. I assume the street name in this instance.
var RH = $feature.R_from
var RL = $feature.R_to
var LH = $feature.L_from
var LL = $feature.L_to

// Filter featureset for matching streetnames
var ObjID = $feature.OBJECTID
var StreetName = $feature.<stname>
var FS = Filter( $featureset, '<insert stname field> = @StreetName AND OBJECTID <> @ObjID ' )
if( Count( FS ) > 0 ){
    FS = Intersects( $feature, FS )
    if( Includes( ['FeatureSet','Feature'], TypeOf(FS) ){
        RL = Min(FS,R_to)
        LL = Min(FS,L_to)
        }
    }

// Compute final union ranges (deleted is SECOND, merged is FIRST)
var final_L_from = toNum(mergedFromL);
var final_L_to   = toNum(deletedToL);
var final_R_from = toNum(mergedFromR);
var final_R_to   = toNum(deletedToR);

// These console messages print out the correct final ranges
Console("Final L From: " + Text(final_L_from));
Console("Final L To: "   + Text(final_L_to));
Console("Final R From: " + Text(final_R_from));
Console("Final R To: "   + Text(final_R_to));

// This console message prints out the correct ID to be updated
Console("Merged Road ID to Update: " + Text(mergedRoad.GlobalID));

// Apply updates to the kept (merged) feature
return {
  'edit': [{
    'className': 'ROADS_StatePlane',
    'updates': [{
      'OBJECTID': mergedRoad.OBJECTID,
      'attributes': {
        'LFROM_Test': final_L_from,
        'LTO_Test':   LL,
        'RFROM_Test': final_R_from,
        'RTO_Test':   RL
      }
    }]
  }]
};

This is just a suggestion and I can give you another but without seeing the full code this should hopefully point you in the right direction.

Another would be to convert the overlapping vertices to points and finding the lowest values based on the points.

0 Kudos
BriannaF
Occasional Contributor

Hi, thanks for the suggestions! I didn't want to bog down the post with the full code so my bad for not making it clear about how I'm identifying the segments. I'm actually already using a filter on street name to get the second segment that's part of the merge along with the deleted segment (which would be the current feature when this attribute rule fires because it fires on Delete). Then, I check that it's the correct segment by verifying that this feature's end point is equal to the deleted feature's start point. So I'm able to successfully identify the 2 features that are part of the merge which was the hardest part to wrap my head around.

My issue now is that despite identifying the correct segments and correctly calculating what the updated address ranges should be from the 2 segments' attributes, it does not correctly update the segment when I try to do a merge. Rather, it keeps the original ranges from the merged segment. So in the screenshot example, the merged segment ends up with the original first segment's ranges of 300-306 and 301-307, instead of the "merged" ranges of 300-398 and 301-399.

I suspect it has something to do with the order of operations of Delete/Update when I do the Merge edit, along with the order of operations of what happens with the attribute rule (which fires on Delete), but I'm not sure.

think the only way for it to work and "grab" the address ranges from the segment that gets deleted in the merge is to have the attribute rule fire on Delete instead of Update, because if it fires on the Updated feature, the attributes from the deleted feature would already be lost. I'm not sure there is a solution to this or if I can get it to work at all, but just thought I'd ask. Thanks for your suggestions!

0 Kudos
RPGIS
by MVP Regular Contributor
MVP Regular Contributor

That makes it a bit more clear now. So what you can do is set the condition to identify the original feature in conjunction with the edit context. With the $originalfeature you can identify the feature before it's deleted and $editcontext.editType to be 'DELETE' so that upon deletion it pulls the original feature and then updates the new feature with the old value

HusseinNasser2
Esri Contributor

I'm not sure if the user workflow allows it but I would suggest using the option to keep the original features  and let the attribute rules do the delete instead of the merge tool. This gives you more control as it turns the operation from a delete , update. To an update only and you will write the rule to trigger on update on geometry (use the triggering fields to set the rule to trigger only on the shape change)

on the update you will have the control to query the original features (since they won't be deleted) and copy any attribute you want off of it. And then later you can insert the logic to actually delete the original feature.

 

HusseinNasser2_0-1761868786004.png

 

My guess as to why you are losing your update is as you suspected the order of the operations causes your update to be lost/ not persisted. 

 

 

0 Kudos
BriannaF
Occasional Contributor

Thanks for the suggestion - I hadn't looked too closely at the merge options so this gives me something to play around with. It looks like it won't be as smooth of a workflow as I was hoping for and will rely on the user having to choose specific settings, but this does give me hope that it might at least be possible conceptually. Thanks!

0 Kudos
HusseinNasser2
Esri Contributor

I authored an example attribute rule to demonstrate this on merge. When the user selects two line features and wants to merge, the attribute rule will pick the largest line segment and copy the addresstext of that line feature to the new merged feature and then the rule will delete the old two segments. 

Hope this helps

 

 

//When merging with the option "new feature" the rule will be executed on insert, if we enabled keep original features we get access to the original rows , this way we can query the merged lines, pick the largest segment , take the address from it and copy it to the new merged line. after merging we issue a delete to delete the two original segements. 
var g = $feature.GlobalID
var fs = contains(filter($featureset, "globalid <> @g"), geometry($feature))
 
var biggerAddress = null;
var biggerLine = null;
var tobeDeleted = []

for (var g in fs) 
   { 
    push(tobeDeleted, {"globalId": g.globalid} )

    if (biggerLine == null){
      biggerLine = g;
      biggerAddress = g.addresstext;
    }
    
    if (length(g) >= length(biggerLine)){
      biggerLine = g;
      biggerAddress = g.addresstext;
    }
      
      
  }


if (biggerLine == null) return;

return {
  "result": {"attributes": {"addresstext": biggerAddress}},
  "edit": [{
    "className": "main.theline",
    "deletes": tobeDeleted
  }]
}

 

 

BriannaF
Occasional Contributor

Awesome, thank you! I will try this out. I appreciate your help!

0 Kudos
RPGIS
by MVP Regular Contributor
MVP Regular Contributor

The only other thing I can think of that would merge the two would be to create rule where if the conditions were met it would do the update and delete for you.

For instance: 

  1. Add a numerical field and set the value to Object ID of the feature you wish to merge
  2. Create a rule where if the field is populated with the OID of the desired feature then set it to do the following
    1. Filter the featureset so that the OID of the merging feature is equal to the OID value of the updated feature
    2. Use the Union function to merge the feature geometries of both features into a single geometry.
    3. Check if the fields for upper/lower address bounds are different than the fields in the updated record of the merging OID. Update the other record using the min/max functions for the filtered featureset.
    4. Return the results such it returns the updated records OID with those values.
    5. Create another rule with the DELETE capability and set it so that it filters using the same filter as the previous rule but will only delete when either the upper/lower address bounds are equal.

@HusseinNasser2 may be able to speak to this but I would like to get his take on it.

0 Kudos