Auto-generating and updating a centroid point layer from a polygon layer

1111
7
12-13-2021 04:47 AM
Labels (1)
KatieSelf
New Contributor III

 

I currently have a webmap which displays a layer generated from responses in Survey123. In the survey users draw a polygon indicating the area of their response. 

In the webmap I would like to display these polygons, as well as points (i.e. the polygon centroid) for each response. The points are clearer to see at a high level and are a better choice for pop-ups. 

Currently I have two copies of the polygon layer (public view version) in the webmap and I'm using the 'Color and Size' style option to make one of the layers 'appear' as points. The issue with this is that I can't utilise features specific to point layers such as clustering. 

Instead I would like to generate a genuine point layer which references all the data from the polygons, and uses the centroid as the point co-ordinates. I know this is something that can be done manually from the layer, but I'd like the points to continue to populate as responses are submitted and new polygons appear.

Is there a mechanism by which this can be done? The only thing I can think of would be utilising a notebook which perhaps is triggered by a survey response, but I'm not familiar with notebooks and I'm unsure about how to go about this and if the credit usage will be reasonable. 

7 Replies
JohannesLindner
MVP Frequent Contributor

Create a new point feature class:

  • name: PolygonCentroids
  • add GlobalID
  • add Field: PolygonGUID, type GUID

Create a relationship class between the polygon feature class and PolygonCentroids:

  • name: RsPolygonCentroids
  • key fields: Polygons.GlobalID, PolygonCentroids.PolygonGUID

Create an attribute rule for your polygon feature class:

// Calculation Attribute Rule on polygon feature class
// field: empty
// triggers: insert, update, delete

// this object will be returned to edit the related point fc
var edit_points = {"className": "PolygonCentroids", "adds": [], "updates": [], "deletes": []}

var mode = $editcontext.editType
var poly_guid = $feature.GlobalID
var p_geometry = Centroid($feature)
var p_attributes = {"PolygonGUID": poly_guid}

if(mode == "INSERT") {
  // if a polygon is inserted, insert a new point
  Push(edit_points.adds, {"geometry": p_geometry, "attributes": p_attributes})
} else {
  // find the related point(s)
  var related_points = FeatureSetByRelationshipName($feature, "RsPolygonCentroids")
  for(var p in related_points) {
    if(mode == "UPDATE") {
      // update geometry
      Push(edit_points.updates, {"globalID": p.GlobalID, "geometry": p_geometry})
    }
    if(mode == "DELETE") {
      // delete point
      Push(edit_points.deletes, {"globalID": p.GlobalID})
    }
  }
}

// apply the edits
return {"edits": [edit_points]}

 

The names are up to you, of course, but you have to change them in the script accordingly.

 

This will give you an automatically updated point feature class with the polygon centroids and the GlobalID of the respective polygons. To get the polygon attributes in the point popup, create an Arcade expression for each attribute you want to show, you can show the results in the popup's attribute table as usual.

var related_polygons = FeatureSetByRelationshipName($feature, "RsPolygonCentroids")
if(Count(related_polygons) > 0) {
  return First(related_polygons).Attribute
}

 


Have a great day!
Johannes
KatieSelf
New Contributor III

Hi Johannes,

Thank you for you answer, I haven't used relation classes before so this looks perfect. I'm afraid I'm having a bit of trouble trying to implement. I tried to set relationship class in ArcPro (referencing the layers hosted on AGOL) but got the following error. 

KatieSelf_0-1639494803121.png

Is there a way to do this directly on AGOL that I'm missing?

 

0 Kudos
JohannesLindner
MVP Frequent Contributor

Ah, I overlooked the AGOL tag...

You can't create a relationship class between AGOL layers. You can only create them between tables/feature classes in geodatabases and then publish the tables. Same with attribute rules.

Honestly, I'm not sure if my approach works in AGOL at all, never used it. I know it works when you publish the layers to Portal and consume them in a web app.


Have a great day!
Johannes
0 Kudos
KatieSelf
New Contributor III

Hi Johannes - thanks for the code anyway. Definitely looks helpful for replicating what I want outside of AGOL. It's a shame that relationship classes aren't supported on AGOL.

0 Kudos
TerrolPalmer6
New Contributor

I am currently trying to implement an attribute rule on a polygon feature class (AG_PL) to add, update or delete a point feature (AG_PT) geometry and populate 'PolygonGUID' field in AG_PT with AG_PL's GlobalID. 

AG_PL and AG_PT are in composite relationship called (AG_PL_AG_PT).

When I test creating a new polygon or moving an existing polygon or deleting an existing polygon I receive this message below. 

I've reviewed the arcade expression several times but am unable to identify the Undefined Keyword.  Any help on this would be much appreciated.  Thanks!

Failed to create 4.
Undefined keyword is used in the dictionary return script. [
Rule name: Poly,
Triggering event: Insert,
Class name: AG_PL,
GlobalID: {D7E577D7-9F8F-4099-AE23-9B5DBCDD3EC3}]Undefined keyword is used in the dictionary return script. [edits]
// Calculation Attribute Rule on polygon feature class
// field: empty
// triggers: insert, update, delete
// Push(edit_points.adds, {"geometry": p_geometry, "attributes": p_attributes})
// this object will be returned to edit the related point fc
var edit_points = {"className": "AG_PT", "adds": [], "updates": [], "deletes": []}

var mode = $editcontext.editType
var poly_guid = $feature.GlobalID
var p_geometry = Centroid($feature)
var p_attributes = {'PolygonGUID': poly_guid}


if (mode == "INSERT") {
  // if a polygon is inserted, insert a new point
  Push(edit_points.adds, {"geometry": p_geometry, "attributes": p_attributes})
} 


else {
  // find the related point(s)
  var related_points = FeatureSetByRelationshipName($feature, 'AG_PL_AG_PT')
  for(var p in related_points) {
    if(mode == "UPDATE") {
      // update geometry
      Push(edit_points.updates, {'globalID': p.GlobalID, 'geometry': p_geometry})
    }
    if(mode == "DELETE") {
      // delete point
      Push(edit_points.deletes, {'globalID': p.GlobalID})
    }
  }
}

// apply the edits
return {"edits": [edit_points]}
0 Kudos
Amarz
by
Occasional Contributor II

Looks like that error is caused by Line 36 

return {"edits": [edit_points]}

should be "edit" 

return {"edit": [edit_points]}​

 

0 Kudos
Amarz
by
Occasional Contributor II

Looks like that error is caused by Line 36 

 

return {"edits": [edit_points]}

 

should be "edit" 

 

return {"edit": [edit_points]}​

 

 

0 Kudos