I have started recently using some of the ArcObjects inside my Python modules. Having all the useful posts and insights shared by @matt wilkie et al, I was able to get started pretty quickly (installing the comtypes with pip and downloading the 10.2 snippet from Pierssen and changing "10.2" to "10.3" everywhere).
I am trying to iterate IFeatureCursor
and get all the features inside a feature class. However, I am getting back only the latest feature (with the highest ObjectID value).
There are 6 features in the feature class hence xrange(6)
to keep it simple.
from comtypes.client import GetModule, CreateObject
from snippets102 import GetStandaloneModules, InitStandalone
# First time through, need to import the "StandaloneModules". Can comment out later.
#GetStandaloneModules()
InitStandalone()
def iterate_features():
# Get the GDB module
esriGeodatabase = GetModule(r"C:\Program Files (x86)\ArcGIS\Desktop10.3\com\esriGeoDatabase.olb")
esriDataSourcesGDB = GetModule(r"C:\Program Files (x86)\ArcGIS\Desktop10.3\com\esriDataSourcesGDB.olb")
# Create a file geodatabase pointer
file_gdb_pointer = CreateObject(progid=esriDataSourcesGDB.FileGDBWorkspaceFactory,
interface=esriGeodatabase.IWorkspaceFactory)
file_gdb = file_gdb_pointer.OpenFromFile(r"C:\GIS\arcobjects\MyData.gdb",hWnd=0)
#access contents inside gdb
feature_workspace = file_gdb.QueryInterface(esriGeodatabase.IFeatureWorkspace)
in_fc = feature_workspace.OpenFeatureClass("Warehouses")
def enum_features(in_fc):
"""returns pointers to IFeature objects inside Feature Class"""
cur = in_fc.Search(None,True)
for i in xrange(6):
feature_obj = yield cur.NextFeature()
feats = [feat for feat in enum_features(in_fc)]
print [f.OID for f in feats]
iterate_features()
The line print [f.OID for f in feats]
returns [6, 6, 6, 6, 6, 6]
.
What am I doing wrong? The same logic with generator/yield (def enum_features()
) works fine when iterating feature classes inside the feature dataset.
the feats_OIDs = [feat.OID for feat in enum_features(in_fc)]
will give correct results, [1, 2, 3, 4, 5, 6]
, without me making any modifications to the code. The problem seems to be in that when I create a list of features [feat for feat in enum_features(in_fc)]
, they all refer to the same feature (because when I explore each of them later, each of them have the same OID).
1 Answer 1
I think a better approach might be to get the count first using the FeatureCount()
method of the IFeatureClass
Interface. This worked for me:
import arcobjects # my copy of snippets
from comtypes.client import CreateObject
import os
pars = r'C:\TEMP\frontage_test.gdb\parcels'
fc = arcobjects.OpenFeatureClass(*os.path.split(pars))
def enum_features(fc):
import comtypes.gen.esriGeoDatabase as esriGeoDatabase
qf = CreateObject(progid=esriGeoDatabase.QueryFilter, interface=esriGeoDatabase.IQueryFilter) #can use NewObj here too if you have it in your snippets
count = fc.FeatureCount(qf)
cur = fc.Search(qf, True)
for i in xrange(count):
yield cur.NextFeature()
for ft in enum_features(fc):
print ft.OID
And from my arcobjects
module, this is my OpenFeatureClass() function:
def OpenFeatureClass(sFileGDB, sFCName):
InitStandalone()
import comtypes.gen.esriGeoDatabase as esriGeoDatabase
import comtypes.gen.esriDataSourcesGDB as esriDataSourcesGDB
pWSF = NewObj(esriDataSourcesGDB.FileGDBWorkspaceFactory, \
esriGeoDatabase.IWorkspaceFactory2)
pWS = pWSF.OpenFromFile(sFileGDB, 0)
pFWS = CType(pWS, esriGeoDatabase.IFeatureWorkspace)
# determine if FC exists before attempting to open
# http://edndoc.esri.com/arcobjects/9.2/ComponentHelp/esriGeoDatabase/IWorkspace2_NameExists.htm
# 5 = feature class datatype
pWS2 = CType(pWS, esriGeoDatabase.IWorkspace2)
if pWS2.NameExists(5, sFCName):
pFC = pFWS.OpenFeatureClass(sFCName)
else:
pFC = None
print '** %s not found' % sFCName
return pFC
EDIT
@Alex Tereshenkov raised the question of how to get this into a list of pointers to IFeature
objects, and this can be done with a list comprehension. So the answer is yes.
>>> features = [ft for ft in enum_features(fc)]
>>> features[:5] # lots of features, so lets just show the first few
[<POINTER(IFeature) ptr=0x2b2d930 at 2c13440>, <POINTER(IFeature) ptr=0x2b2d930 at 2c13490>, <POINTER(IFeature) ptr=0x2b2d930 at 32f5210>, <POINTER(IFeature) ptr=0x2b2d930 at 32f5260>, <POINTER(IFeature) ptr=0x2b2d930 at 32f52b0>]
>>>
EDIT 2:
I found the problem. We do not actually want to recycle the rows. Once I changed that to false, we can get out each IFeature
into a list.
cur = fc.Search(None, False) #do not recycle this IFeature object!
So now when you do this you should get an object for each row:
features = [ft for ft in enum_features(fc)]
print [ft.OID for ft in features[:5]]
This is laid out in the help docs:
The recycling parameter controls row object allocation behavior. Recycling cursors rehydrate a single feature object on each fetch and can be used to optimize read-only access, for example, when drawing. It is illegal to maintain a reference on a feature object returned by a recycling cursor across multiple calls to NextFeature on the cursor. Features returned by a recycling cursor should not be modified. Non-recycling cursors return a separate feature object on each fetch. The features returned by a non-recycling cursor may be modified and stored with polymorphic behavior.
The Geodatabase guarantees "unique instance semantics" on non-recycling feature objects fetched during an edit session. In other words, if the feature retrieved by a search cursor has already been instantiated and is being referenced by the calling application, then a reference to the existing feature object is returned.
Non-recycling feature cursors returned from the Search method MUST be used when copying features from the cursor into an insert cursor of another class. This is because a recycling cursor reuses the same geometry and under some circumstances all of the features inserted into the insert cursor may have the same geometry. Using a non-recycling cursor ensures that each geometry is unique.
-
1No problem! And yes, you can create a list of pointers to
IFeature
objects with the list comprehension as you had it set up. See my edit.crmackey– crmackey2015年12月04日 18:07:58 +00:00Commented Dec 4, 2015 at 18:07 -
1Nevermind, that didn't work...I am having the same issue as you where I have the same OID. I'll take another look after lunch. The iterator seems to be acting weird.crmackey– crmackey2015年12月04日 18:12:58 +00:00Commented Dec 4, 2015 at 18:12
-
1I found the problem! The problem was we were re-hydrating the row object by recycling it and using the same piece of memory for the row (
IFeature
) pointers. Once you disable recycling, you can get an object for each row. See my edit 2.crmackey– crmackey2015年12月04日 19:32:14 +00:00Commented Dec 4, 2015 at 19:32 -
1Works fine for me now. Thanks a lot, mate. Now I can sleep peacefully :DAlex Tereshenkov– Alex Tereshenkov2015年12月04日 19:41:37 +00:00Commented Dec 4, 2015 at 19:41
-
1Haha no problem! And I know what you mean! That was driving me crazy too; I couldn't figure out why I kept getting the same row object back. I edited my "edit 2" again to add the relevant documentation that spells it out.crmackey– crmackey2015年12月04日 19:42:53 +00:00Commented Dec 4, 2015 at 19:42
feature_obj = cur.NextFeature()
thenyield feature_obj
).