I have a script that is supposed to replace the geometry of a polygon with a new polygon constructed by reading some of its fields (minX, minY, maxX, maxY).
I can do this with traditional arcpy, but I'm running into issues when trying to do it with the arcgis module.
Specifically, I either get no edits to the geometry if I include a spatial reference OR I get a "read-only" error if I include the spatial reference.
The documentation does not have any examples of actually updating geometry the way that you can update attributes.
Does anyone have any pointers? Thanks
# Get inspectbl as arcgis Featureset
inspectFL = arcgis.features.FeatureLayer(inspectbl,
gis=portal)
inspectFS = inspectFL.query(where=where_clause)
spatref = inspectFS.spatial_reference
for insp in inspectFS:
# Convert arcgis.feature geometry into
# arcpy Geometry.
lowleft = [insp.attributes["mapminx"],
insp.attributes["mapminy"]]
upleft = [insp.attributes["mapminx"],
insp.attributes["mapmaxy"]]
upright = [insp.attributes["mapmaxx"],
insp.attributes["mapmaxy"]]
lowright =[insp.attributes["mapmaxx"], insp.attributes["mapminy"]]
corners = [lowleft, upleft, upright, lowright]
geo = arcgis.geometry.Polygon(corners, spatial_reference=spatref)
geo = arcgis.geometry.Geometry({"rings":corners,
"spatial_reference": {"wkid":spatref}})
insp.set_value(field_name="shape",
value= geo)
inspectFL.edit_features(updates=inspectFS,
use_global_ids=True)
Solved! Go to Solution.
Ok, so as it turns out, it was not an issue of the final point, although I've added that just in case.
The actual issue was that I was not nesting the coordinates deep enough.
The "rings" needs to be a list of lists of coordinate pairs, like:
[[[x1,y1], [x2,y2]]]
I had it as
[[x1,y1, [x2, y2]]
Final working is
# Get inspectbl as arcgis Featureset
inspectFL = arcgis.features.FeatureLayer(inspectbl,
gis=portal)
inspectFS = inspectFL.query(where=where_clause)
spatref = inspectFS.spatial_reference
for insp in inspectFS:
# Convert arcgis.feature geometry into
# arcgis.geometry.Geometry.
lowleft = [insp.attributes["mapminx"],
insp.attributes["mapminy"]]
upleft = [insp.attributes["mapminx"],
insp.attributes["mapmaxy"]]
upright = [insp.attributes["mapmaxx"],
insp.attributes["mapmaxy"]]
lowright =[insp.attributes["mapmaxx"], insp.attributes["mapminy"]]
corners = [[lowleft, upleft, upright, lowright, lowleft]]
geo = arcgis.geometry.Geometry({"rings":corners,
"spatial_reference":
{"wkid":spatref}})
insp.set_value(field_name="shape",
value= geo)
x = inspectFL.edit_features(updates=inspectFS,
use_global_ids=True)
Thanks everyone
Edit: read the original post wrong so never mind 😅 I see you're reading the coordinates from the attributes and not generating from the geometry. I'll wake up at some point today.
Anyway, you can still use the Envelope to help build the polygons rather that messing around with nesting.
from arcgis.geometry import Geometry, Envelope, SpatialReference
## get attribute values
minx = insp.attributes["mapminx"]
maxx = insp.attributes["mapmaxx"]
miny = insp.attributes["mapminy"]
maxy = insp.attributes["mapmaxy"]
## get srs
spatref = inspectFS.spatial_reference
## create the polygon from an Envelope object
polygon = Envelope(
{
'xmin': minx, 'xmax': maxx, 'ymin': miny, 'ymax': maxy,
'spatialReference' : SpatialReference(spatref)
}
).polygon
## create the Geometry object
geo = Geometry(polygon)
Original reply: You don't need to build the geometry definition yourself, the API can handle that with the Geometry and Envelope classes. This handles the geometry and the same srs.
from arcgis.gis import GIS
from arcgis.features import FeatureLayer
from arcgis.geometry import Geometry
## access AGOL or Portal
agol = GIS("home")
## feature layer url
fl_url = "FL_URL"
## create a FeatureLayer object
fl = FeatureLayer(
url = fl_url,
gis = agol
)
## query the polygons to update
fset = fl.query()
## update the geometry to the bbox / envelope
for feature in fset.features:
feature.geometry = Geometry(feature.geometry).envelope.polygon
## update the features
fl.edit_features(
updates = fset
)
All the best,
Glen
Might be worth using the as_dict instance method and from_dict class method to create a new feature with matching attributes instead of editing the original object. Heavy handed but if it works, it works!
I might be mistaken about this, but I don't think your polygons are valid. I'm pretty sure the first point needs to be repeated as the last point to close it out, like this:
corners = [lowleft, upleft, upright, lowright, lowleft]
That's an interesting thought and I'll check it out, but it works with only four points in normal arcpy
with arcpy.da.UpdateCursor(inspectbl,
['SHAPE@', "mapminx", "mapminy",
"mapmaxx", "mapmaxy", "OID@"],
where_clause= where_clause) as cursor:
for row in cursor:
lowleft = arcpy.Point(row[1], row[2])
upleft = arcpy.Point(row[1], row[4])
upright = arcpy.Point(row[3], row[4])
lowright = arcpy.Point(row[3], row[2])
corners = arcpy.Array([lowleft, upleft, upright, lowright])
row[0] = arcpy.Geometry("polygon", corners)
cursor.updateRow(row)so I'm not certain that's it
Ok, so as it turns out, it was not an issue of the final point, although I've added that just in case.
The actual issue was that I was not nesting the coordinates deep enough.
The "rings" needs to be a list of lists of coordinate pairs, like:
[[[x1,y1], [x2,y2]]]
I had it as
[[x1,y1, [x2, y2]]
Final working is
# Get inspectbl as arcgis Featureset
inspectFL = arcgis.features.FeatureLayer(inspectbl,
gis=portal)
inspectFS = inspectFL.query(where=where_clause)
spatref = inspectFS.spatial_reference
for insp in inspectFS:
# Convert arcgis.feature geometry into
# arcgis.geometry.Geometry.
lowleft = [insp.attributes["mapminx"],
insp.attributes["mapminy"]]
upleft = [insp.attributes["mapminx"],
insp.attributes["mapmaxy"]]
upright = [insp.attributes["mapmaxx"],
insp.attributes["mapmaxy"]]
lowright =[insp.attributes["mapmaxx"], insp.attributes["mapminy"]]
corners = [[lowleft, upleft, upright, lowright, lowleft]]
geo = arcgis.geometry.Geometry({"rings":corners,
"spatial_reference":
{"wkid":spatref}})
insp.set_value(field_name="shape",
value= geo)
x = inspectFL.edit_features(updates=inspectFS,
use_global_ids=True)
Thanks everyone
Edit: read the original post wrong so never mind 😅 I see you're reading the coordinates from the attributes and not generating from the geometry. I'll wake up at some point today.
Anyway, you can still use the Envelope to help build the polygons rather that messing around with nesting.
from arcgis.geometry import Geometry, Envelope, SpatialReference
## get attribute values
minx = insp.attributes["mapminx"]
maxx = insp.attributes["mapmaxx"]
miny = insp.attributes["mapminy"]
maxy = insp.attributes["mapmaxy"]
## get srs
spatref = inspectFS.spatial_reference
## create the polygon from an Envelope object
polygon = Envelope(
{
'xmin': minx, 'xmax': maxx, 'ymin': miny, 'ymax': maxy,
'spatialReference' : SpatialReference(spatref)
}
).polygon
## create the Geometry object
geo = Geometry(polygon)
Original reply: You don't need to build the geometry definition yourself, the API can handle that with the Geometry and Envelope classes. This handles the geometry and the same srs.
from arcgis.gis import GIS
from arcgis.features import FeatureLayer
from arcgis.geometry import Geometry
## access AGOL or Portal
agol = GIS("home")
## feature layer url
fl_url = "FL_URL"
## create a FeatureLayer object
fl = FeatureLayer(
url = fl_url,
gis = agol
)
## query the polygons to update
fset = fl.query()
## update the geometry to the bbox / envelope
for feature in fset.features:
feature.geometry = Geometry(feature.geometry).envelope.polygon
## update the features
fl.edit_features(
updates = fset
)
All the best,
Glen
That is a much nicer way to accomplish it, thank you.