Accessing Feature Class Objects from a Geodatabase with ArcPy

8407
8
07-28-2019 01:39 PM
ThomasBlankinship
New Contributor

Is there a way to access the feature class objects directly from a geodatabase so that they can be passed into a function?

I need to convert a batch of .kml files to .gdbs and then remove attribute fields I don't need from the resulting File Geodatabase Feature Classes. To do this I used the KMLToLayer_conversion tool and that worked fine. It created a geodatabase (and .lyrx file) for each .kml file and in ArcGIS Pro the Points and Polylines FGDB Feature Classes show up under a folder called "Placemarks" in the Catolog's folder explorer. Everything is A ok at this point.

I then tried to use the DeleteField_management tool to delete the attribute field names I don't want. This tool takes a table, feature class, feature layer, or raster dataset as its first argument and a list of attribute names to be deleted as its second argument. The problem is, I don't know how to access the FGDB Feature Classes in order to pass them to this function. I have tried a bunch of different things to no avail. The following is my code:

import arcpy as ap
import os

field_list =  ["FolderPath", "SymbolID", "AltMode", "Base", "TimeSpan", "TimeStamp", "EndTime", "Snippet"]

ap.env.workspace = "G:\\My Drive\\GDBs"

for gdb in ap.ListFiles():
            ap.env.workspace = os.path.join("G:\\My Drive\\GDBs", gdb, "Placemarks")
            ap.DeleteField_management([ap.ListFeatureClasses], "field_list")

This gives me the following errors:

runfile('G:/My Drive/One Shared Story/Python Scripts/DELETEFIELDS.py', wdir='G:/My Drive/Python Scripts')
Traceback (most recent call last):

File "<ipython-input-7-f9a0af40c03f>", line 1, in <module>
runfile('G:/My Drive/Python Scripts/DELETEFIELDS.py', wdir='G:/My Drive/Python Scripts')

File "C:\Users\Tab5a\AppData\Local\ESRI\conda\envs\arcgispro-py3-clone\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 827, in runfile
execfile(filename, namespace)

File "C:\Users\Tab5a\AppData\Local\ESRI\conda\envs\arcgispro-py3-clone\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 110, in execfile
exec(compile(f.read(), filename, 'exec'), namespace)

File "G:/My Drive/Python Scripts/DELETEFIELDS.py", line 13, in <module>
ap.DeleteField_management([ap.ListFeatureClasses], ["FolderPath"])

File "C:\Program Files\ArcGIS\Pro\Resources\ArcPy\arcpy\management.py", line 4449, in DeleteField
raise e

File "C:\Program Files\ArcGIS\Pro\Resources\ArcPy\arcpy\management.py", line 4446, in DeleteField
retval = convertArcObjectToPythonObject(gp.DeleteField_management(*gp_fixargs((in_table, drop_field), True)))

File "C:\Program Files\ArcGIS\Pro\Resources\ArcPy\arcpy\geoprocessing\_base.py", line 506, in <lambda>
return lambda *args: val(*gp_fixargs(args, True))

RuntimeError: Object: Error in executing tool

Note that I have tried without the "Placemarks" folder as it does not "exist" if not viewed from ArcGIS Pro. Also note that in the for loop, ap.ListFeatureClasses gives the list ["Points", "Polylines"]. I have also tried to pass the string path into the parameter to no avail (G:\My Drive\GDBs\example.gdb\Placemarks\Points and G:\My Drive\GDBs\example.gdb\Placemarks\Polylines). The problem seems to be converting between an ArcObject and a PythonObject. I'm not sure that the tool is using an object, because the ["Points", "Polylines"] list returned by ap.ListFeatureClasses is populated by strings.

Is there a way I can access the feature class object itself and pass it into this function? Or am I just being dense about something here? Any help would be greatly appreciated. 

0 Kudos
8 Replies
DanPatterson_Retired
MVP Emeritus

You need to fix this first

 ap.DeleteField_management([ap.ListFeatureClasses], "field_list")

You will need to get the list of featureclasses ( for fc in featureclasses )

Then for each featureclass (fc), get a list of fields

Compare each field to the field names you want to delete ( field_names = [f.name for f in arcpy.ListFields(featureclass)] )

Then if the your field is in the deletable fields... delete it.  You can use a variety of methods to check if the featureclass has the fields that you want to delete... for example (assume line 3 is the result of listFields

del_flds = ['a', 'b', 'c']

all_flds = list('abcdefghi')

remove_these = list(set(all_flds).intersection(del_flds))

remove_these

['c', 'b', 'a']

ListFeatureClasses—ArcPy Functions | ArcGIS Desktop 

ListFields—ArcPy Functions | ArcGIS Desktop 

Delete Field—Data Management toolbox | ArcGIS Desktop 

ThomasBlankinship
New Contributor

Ok, well that gives me my list to delete (that wasn't meant to be in quotes by the way, did a bit of editing here before posting and messed up) but it still doesn't solve the problem of what to pass into the function for the input feature class.

I now have:

import arcpy as ap
import os

ap.env.workspace = "G:\\My Drive\\GDBs"

del_fields = ["FolderPath", "SymbolID", "AltMode", "Base", "TimeSpan", "TimeStamp", "EndTime", "Snippet", "HasLabel", "LabelID", "Clamped", "Extruded"]


for gdb in ap.ListFiles():
            ap.env.workspace = os.path.join("G:\\My Drive\\GDBs", gdb, "Placemarks")
            featureclasses = ap.ListFeatureClasses()
            for fc in featureclasses:
                        all_fields = [f.name for f in ap.ListFields(fc)]
                        remove_these = list(set(all_fields).intersection(del_fields))
                        ap.DeleteField_management(fc, remove_these)

The error still occurs. All fields in del_fields are deletable, I checked in ArcGIS Pro by hand. It seems to not be passing the actual feature to the function. At least that is my guess.

0 Kudos
LukeWebb
Occasional Contributor III

Firstly, "Placemarks" is not a folder, its what is called a 'feature dataset'.

These are not meant to be used as folders, so if you are using them for this then that is not what they are created for. (Research these separately to find there uses)

Then we go to https://pro.arcgis.com/en/pro-app/arcpy/functions/listfeatureclasses.htm  and can see that it is expecting data in this structure:

ListFeatureClasses ({wild_card}, {feature_type}, {feature_dataset})

It uses your environment path, that you set like this:

 ap.env.workspace = os.path.join("G:\\My Drive\\GDBs", gdb, "Placemarks")

As feature datasets are not workspaces, we should instead set our path to::

 ap.env.workspace = os.path.join("G:\\My Drive\\GDBs", gdb)

Now, seeing the structure of ListFeatureClasses above, we could limit our results to the placemarks feature dataset,  by calling list FCs as:

featureclasses = ap.ListFeatureClasses(feature_dataset='Placemarks')

 

Now ListFCs should return some data. (It was probably returning nothing with what you had above)

Try to print it out (Good way to test your script and learn whats happening!), and it would provide a list of featurecalsses, as strings. (example:  ['dataset1', 'dataset2']

To delete fields, as you say we should get a "reference" to the objects themselves instead of strings. Alternatively, we could just provide full paths to the dataset.

To get this, we could do something like:

full_path = os.path.join("G:\\My Drive\\GDBs", gdb, fc)

and finally call the delete, using this path. (Instead of just a string that doesnt tell the tool where your data is)

 ap.DeleteField_management(full_path, remove_these)

ThomasBlankinship
New Contributor

I appreciate the detailed response. I tried the full path method both with "Placemarks" as a folder and without but didn't see that you could reference a particular dataset when looking for feature classes in a geodatabase with ListFeatureClasses(). When I used ListFeatureClasses() on the gdb without referencing a dataset my print statement always returned an empty list, whereas if I treated "Placemarks" as a folder withing the gdb it returned "Points" and "Polylines."

I will edit my code now with your suggestions and see if it works - unfortunately I think something else is going on with my system as well that has hindered my ability to properly test my code. I'm editing in Spyder from a cloned copy of the default ArcGIS Pro python environment and it's throwing errors on code that I know works properly because a) I've used it before and b) it works from within the python console in ArcGIS Pro. No clue what is causing that, will update with results.

0 Kudos
LukeWebb
Occasional Contributor III

Yes im not entirely sure on the workspace / folder implications of feature datasets so a bit of experimentation may be required around that and what I said may not be quite right. 

To prepare a 'proper' layer like what we use in arcmap, we would do this:

newlayer = arcpy.mapping.Layer(path_to_shapefile_or_feature_class)

Then you could use newLayer, as the variable for input to the tool.

But, for a delete fields mgmt tool, you may as well skip that line and provide the full paths which will effectively create the layer as above behind the scenes for the purposes of processing as theres not much addiional properties required for this tool 

the layer method however could have advantages, for example if you wanted to apply a defquery before processing, we could now do this:

newlayer = arcpy.mapping.Layer(path_to_shapefile_or_feature_class)
newlayer.definitionQuery = "[DISTRICT] = '1' AND [COUNTY] = 'ORANGE'"  
next_process = processingTool(newlayer)
0 Kudos
ThomasBlankinship
New Contributor

I feel like I am losing my mind. Again, thanks for the assistance. I feel I need another pair of eyes on this. Hopefully I have just been looking at it for too long and there is an easy fix. Here is my revised code:

import arcpy as ap
import os


gdb_dir = "G:\\My Drive\\GDBs"

del_fields = ["FolderPath", "SymbolID", "AltMode", "Base", "TimeSpan", "TimeStamp", "EndTime", "Snippet", "HasLabel",                      "LabelID", "Clamped", "Extruded"]

ap.env.workspace = gdb_dir
gdb_files = ap.ListFiles()

for gdb in gdb_files:
            ap.env.workspace = os.path.join(gdb_dir, gdb)
            featureclasses = ap.ListFeatureClasses(feature_dataset = "Placemarks")
            for fc in featureclasses:
                        full_path = os.path.join(gdb_dir, gdb, fc)
                        all_fields = [f.name for f in ap.ListFields(fc)]
                        remove_these = list(set(all_fields).intersection(del_fields))
                        ap.DeleteField_management(full_path, remove_these)

The error I'm getting now from the python console in ArcGIS Pro (which could have something to do with what's going on, I have no idea, but it won't work in Spyder for some reason at all):

Traceback (most recent call last):
File "<string>", line 19, in <module>
File "c:\program files\arcgis\pro\Resources\arcpy\arcpy\management.py", line 4449, in DeleteField
raise e
File "c:\program files\arcgis\pro\Resources\arcpy\arcpy\management.py", line 4446, in DeleteField
retval = convertArcObjectToPythonObject(gp.DeleteField_management(*gp_fixargs((in_table, drop_field), True)))
File "c:\program files\arcgis\pro\Resources\arcpy\arcpy\geoprocessing\_base.py", line 506, in <lambda>
return lambda *args: val(*gp_fixargs(args, True))
arcgisscripting.ExecuteError: Failed to execute. Parameters are not valid.
ERROR 000735: Drop Field: Value is required
Failed to execute (DeleteField).

The interesting thing is it works for one and exactly one geodatabase in the gdb_dir folder and then it crashes. So, if I'm reading it right it thinks the Drop Field value (second value in the DeleteField_management function) is invalid or nonexistent after it loops through the first geodatabase's Point and Polyline feature classes. I've tried printing everything and I get what I would expect: print statements after ap.env.workspace loop through the geodatabase paths, featureclasses perpetually gives me ['Points', 'Polylines'], the full_path loops through both feature classes in each geodatabase, all_fields alternates between the fields present in the Points and Polylines feature classes, and remove_these alternates between the fields I want deleted in the Points and Polylines feature classes. I am at an absolute and utter loss.

0 Kudos
JoshuaBixby
MVP Esteemed Contributor

This line is incorrect:

full_path = os.path.join(gdb_dir, gdb, fc)

You should print that line, and you will see what I am talking about.  As written, your full path will include the GDB and FC names, but it doesn't include any reference to the feature dataset, so the Delete Field tool is looking for a FC in the root directory and not in the feature dataset.

As a quick hack, you could try:

full_path = os.path.join(gdb_dir, gdb, "Placemarks", fc)

0 Kudos
LukeWebb
Occasional Contributor III

The error mentions 'drop field, requires a value' so I imagine it might not be a path issue.

This probably means, that your 'intersection' of fields between delete these and the fcs, is returning none. So you are passing in effectively no parameter! 

You could try something along lines of:

If len(remove_fields) >0:

     Remove them

0 Kudos