9

By far, Accessing ArcObjects from Python? is my most read and referenced Q&A on GIS Stack Exchange. In spite of that success, it's probably one of my weakest areas when it comes to actual use. A large part of that poor showing derives from my poor ability to read and understand the ArcObjects docs.

So, for any given task what are some guidelines for translating .net/c++/java/... docs and examples into their python equivalents? (which language is the best one to work from for that matter?) and what is the best index or landing page to start from? what stuff should be focussed on, and likely at least as important, what can be freely ignored?

Assume your audience is at least somewhat python literate, and illiterate in other development languages. Walk us through a small coding exercise, from initial idea and research to working python results.

  • 1
    It may not add anything to the conversation here but I want to state for the record I'd be really interested in seeing this set of walkthroughs develop. Thanks Matt. I found one article by Darren Wiens creating an MXD from scratch and populating the layout with guides. It seems also that Mark Cederholm's snippets module is really helpful/often used in these efforts. – Jim Jan 7 '15 at 20:12
  • A possible example to use: gis.stackexchange.com/questions/86007/… (disclosure: it's the problem I've been working on, that prompted the Q. Beat me to the (well-crafted) answer, get all the credit! ;-) – matt wilkie Jan 7 '15 at 20:15
  • Arcobjects can be difficult to get into, the help docs are OK but the examples are better: One of the biggest problems is working out the inheritance of one object to another, like I've got object X, now how do I get object Y? If you can get your hands on Visual Studio 2008 or 2010 express (free download if you can find it) then install the SDK you will get the help docs and a bunch of examples locally. – Michael Stimson Jan 7 '15 at 22:44
  • 1
    @mattwilkie hopefully this doesn't muddy the waters tooo much... but for porting existing .NET code to python and figuring out type casting syntax, python for .NET looks a little more straight forward than the comtypes approach. That said, I've only just discovered python for .NET and haven't tested it yet. – user2856 Jan 8 '15 at 0:56
  • 1
    @mattwilkie have just discovered python.Net requires the ArcGIS SDK be installed (unless the assembly wrapper dlls are distributed with the script...) in addition to ArcGIS Desktop, so not quite as portable as the comtypes approach. – user2856 Jan 8 '15 at 2:02
9

I am not very strong in this area either, but I have modified the Snippets module and have made a couple wrappers for very simple tasks. I have an example of just adding line elements. The example at under the main block forms a triangle to the layout view just outside the document.

I use this script in conjunction with another and arcpy search cursors to make graphic tables in the layout from individual lines and text elements, but that quickly moves away from the "simple" example. The code below is fairly simple and uses a modified version of snippets:

from snippets import *
def add_line(pApp=None, name='Line', x=None, y=None, end_x=None, end_y=None,
             x_len=0, y_len=0, anchor=0, view='layout'):
    '''adds a line to an ArcMap Document

    Required:
    pApp -- reference to either open ArcMap document or path on disk
    name -- name of line element

    Optional:
    x -- start x coordinate, if none, middle of the extent will be used (data view)
    y -- start y coordinate, if none, middle of the extent will be used (data view)
    end_x -- end x coordinate, if making straight lines use x_len
    end_y -- end y coordinate, if making straight lines use y_len
    x_len -- length of line in east/west direction
    y_len -- length of line in north/south direction
    anchor -- anchor point for line element
    view -- choose view for text element (layout|data)

        Anchor Points:
        esriTopLeftCorner   0   Anchor to the top left corner.
        esriTopMidPoint     1   Anchor to the top mid point.
        esriTopRightCorner  2   Anchor to the top right corner.
        esriLeftMidPoint    3   Anchor to the left mid point.
        esriCenterPoint     4   Anchor to the center point.
        esriRightMidPoint   5   Anchor to the right mid point.
        esriBottomLeftCorner    6   Anchor to the bottom left corner.
        esriBottomMidPoint  7   Anchor to the bottom mid point.
        esriBottomRightCorner   8   Anchor to the botton right corner.
    '''
    GetDesktopModules()
    import comtypes.gen.esriFramework as esriFramework
    import comtypes.gen.esriArcMapUI as esriArcMapUI
    import comtypes.gen.esriSystem as esriSystem
    import comtypes.gen.esriGeometry as esriGeometry
    import comtypes.gen.esriCarto as esriCarto
    import comtypes.gen.esriDisplay as esriDisplay
    import comtypes.gen.stdole as stdole

    # set mxd
    if not pApp:
        pApp = GetApp()
    pDoc = pApp.Document
    pMxDoc = CType(pDoc, esriArcMapUI.IMxDocument)
    pMap = pMxDoc.FocusMap
    pMapL = pMap
    if view.lower() == 'layout':
        pMapL = pMxDoc.PageLayout
    pAV = CType(pMapL, esriCarto.IActiveView)
    pSD = pAV.ScreenDisplay

    # set coords for elment
    pFact = CType(pApp, esriFramework.IObjectFactory)
    if view.lower() == 'data':
        pEnv = pAV.Extent
        if x == None:
            x = (pEnv.XMin + pEnv.XMax) / 2
        if y == None:
            y = (pEnv.YMin + pEnv.YMax) / 2
    else:
        # default layout position, move off page
        if x == None: x = -4
        if y == None: y = 4

    # from point
    pUnk_pt = pFact.Create(CLSID(esriGeometry.Point))
    pPt = CType(pUnk_pt, esriGeometry.IPoint)
    pPt.PutCoords(x, y)

    # to point
    pUnk_pt2 = pFact.Create(CLSID(esriGeometry.Point))
    pPt2 = CType(pUnk_pt2, esriGeometry.IPoint)
    if x_len or y_len:
        pPt2.PutCoords(x + x_len, y + y_len)
    elif end_x or end_y:
        pPt2.PutCoords(end_x, end_y)

    # line (from point - to point)
    pUnk_line = pFact.Create(CLSID(esriGeometry.Polyline))
    pLg = CType(pUnk_line, esriGeometry.IPolyline)
    pLg.FromPoint = pPt
    pLg.ToPoint = pPt2

    # preset color according to RGB values
    pUnk_color = pFact.Create(CLSID(esriDisplay.RgbColor))
    pColor = CType(pUnk_color, esriDisplay.IRgbColor)
    pColor.Red, pColor.Green, pColor.Blue = (0,0,0) #black line

    # set line properties
    pUnk_line = pFact.Create(CLSID(esriDisplay.SimpleLineSymbol))
    pLineSymbol = CType(pUnk_line, esriDisplay.ISimpleLineSymbol)
    pLineSymbol.Color = pColor

    # create the actual element
    pUnk_elm = pFact.Create(CLSID(esriCarto.LineElement))
    pLineElement = CType(pUnk_elm, esriCarto.ILineElement)
    pLineElement.Symbol = pLineSymbol
    pElement = CType(pLineElement, esriCarto.IElement)

    # elm properties
    pElmProp = CType(pElement, esriCarto.IElementProperties3)
    pElmProp.Name = name
    pElmProp.AnchorPoint = esriCarto.esriAnchorPointEnum(anchor)
    pElement.Geometry = pLg

    # add to map
    pGC = CType(pMapL, esriCarto.IGraphicsContainer)
    pGC.AddElement(pElement, 0)
    pGCSel = CType(pMapL, esriCarto.IGraphicsContainerSelect)
    pGCSel.SelectElement(pElement)
    iOpt = esriCarto.esriViewGraphics + \
    esriCarto.esriViewGraphicSelection
    pAV.PartialRefresh(iOpt, None, None)
    return pElement

if __name__ == '__main__':

    # testing (make a triangle)
    add_line(name='hypot', end_x=-2, end_y=2, anchor=3)
    add_line(name='vertLine', y_len=-2, anchor=1)
    add_line(name='bottom', y=2, end_x=-2, end_y=2)

enter image description here

Edit:

@matt wilkie

As for figuring out the imports, that is where you will have to look over the ArcObjects Model Diagrams or see which namespace a particular Class or Interface is being called from in the .NET SDK help docs. In some cases more than one namespace can be used because of inheritance.

I'm no expert in ArcObjects, so it usually takes me a while to figure out when to cast things with CType(). Most of this, I have picked up from samples online. Also, the syntax from the VB.NET examples seems to be closer to what you do in Python, but the C# examples make more sense to me in terms of readability (if that makes any sense). But, as a rule of thumb I usually follwo these steps:

  1. Create a variable for a new COM object (usually a class) to instantiate an object
  2. Use CType to cast the COM object to an interface(s) to allow access to methods and proerties. CType will also return the comtypes Interface Pointer via the QueryInterface(). Once the pointer is returned, you can then interact with its properties and methods.

Not sure if I'm using the proper terminology or not...I am primarily a Python developer who "dabbles" in some ArcObjects...I've only touched the tip of the iceberg though.

Also, this helper function will load all of the ArcObjects Object Libraries (.olb):

def load_all():
    '''loads all object libraries'''
    from comtypes.client import GetModule
    mods = glob.glob(os.path.join(GetLibPath(), '*.olb'))
    for mod in mods:
        GetModule(mod)
    return


def GetLibPath():
    '''Reference to com directory which houses ArcObjects
    Ojbect Libraries (*.OLB)'''
    return glob.glob(os.path.join(arcpy.GetInstallInfo()['InstallDir'], 'com'))[0]
  • thanks for the useful example! The thrust of the Q is (intended to be) less on specific task recipes and more on how does one get and write the info to build the recipe in the first place. For instance, how did you know to import comtypes.gen.esriArcMapUI as esriArcMapUI and then later use in pMxDoc = CType(pDoc, esriArcMapUI.IMxDocument) (and uncover the syntax in that statement)? – matt wilkie Jan 7 '15 at 22:36
  • I edited my original answer to try to answer your questions. I have a few other examples as well, but the above snippet is probably the most readable. – crmackey Jan 7 '15 at 23:16
  • Also, I purchased this book last year: amazon.com/Beginning-ArcGIS-Desktop-Development-using/dp/… – crmackey Jan 7 '15 at 23:34
7

In another, related but slightly different, post I provided an answer that may be of interest for python users trying to wrap their heads around the Esri ArcObjects help docs..

I came from the other side: I already knew ArcObjects long (long long) before I had even heard of python and thanks to posts like these I am able to include some critical ArcObjects in the easy scripting of python (see this post for an example). I do remember the frustration of trying to understand inheritance, methods and properties; dilemmas like I've got X which is sort of related to Y... so how do I get from X to Y.Method()?

The answer is to look at the CoClasses that implement the interface (see full text here).. for a basic example, if I want to see if a layer has a definition query, and if so what is it:

In C#:

ILayer pLayer = pMap.get_Layer(LayerIndex);
IFeatureLayer pFtLayer = pLayer as IFeatureLayer; // also written pFtLayer = (IFeatureLayer) pLayer
IFeatureLayerDefinition pFtLayDef = (IFeatureLayerDefinition)pFtLayer; // also works as pFtLayDef = pFtLayer as IFeatureLayerDefinition;
if (pFtLayDef.DefinitionExpression.Length == 0)
    Console.WriteLine("No definition query");
else
    Console.WriteLine("Query is " + pFtLayDef.DefinitionExpression);

Instead of ctype (which is prominent in VB) C# uses () or as for casting, for example IObject x = (IObject)y; is (fundamentally) the same as IObject x = y as IObject; which would be dim x as IObject = ctype(y,IObject) in VB.

I can tell that I need an IFeatureLayer to get to IFeatureLayerDefinition because: enter image description here

And when you read the help doc for IFeatureLayer you see: enter image description here

Which indicates that it's safe to go ILayer->IFeatureLayer->IFeatureLayerDef, provided that the ILayer is of the type FeatureLayer (or any of the other CoClasses).

So what's up with the I's and no I's? The I means interface, it's the bit that does the work, without an I is a CoClass (a type), so anything that you want to actually use should start with an I and if you're creating a new one or checking the type then skip the I. An interface can have many CoClasses and a CoClass can support many interfaces but it's the interface that actually does the work.

In python:

# I'm assuming arcpy is already imported and comtypes installed
from comtypes.client import GetModule, CreateObject
mC = GetModule(r'C:\Your path\Desktop10.1\com\esriCarto.olb')
mU = GetModule(r'C:\Your path\Desktop10.1\com\esriArcMapUI.olb')
mF = GetModule(r"C:\Your path\Desktop10.1\com\esriFramework.olb")

import comtypes.gen.esriCarto as esriCarto
import comtypes.gen.esriFramework as esriFramework
import comtypes.gen.esriArcMapUI as esriArcMapUI

app = CreateObject(mF.AppROT, interface=mF.IAppROT) # a reference to the ArcMap application
pDoc = ctype(app.Item(0).Document,mU.IMxDocument)   # a reference to the current document
pMap = pDoc.FocusMap # the currently active map
pLayer = pMap.get_layer(LayerIndex)
pFtLayer = ctype(pLayer,esriCarto.IFeatureLayer)
pFtLayDef = ctype(pFtLayer,esriCarto.IFeatureLayerDefinition)
if len(pFtLayDef.DefinitionExpression) == 0:
    print("No definition expression")
else:
    print("Query is " + pFtLayDef.DefinitionExpression)

This sample does slightly more than the C in that it finds its way to the current application, which would only be available in the python window or an addin, if you tried running this from the command line the application is Null and the script would then crash with a null reference exception.

  • Wow, thank you so much for posting this! I have had some struggles understanding the ArcObject Diagrams. It is nice to have some input from someone like yourself who comes from the other side of the fence (lots of .NET ArcObjects experience). One thing I have had some difficulties with is accessing a feature class that resides in a feature dataset via comtypes and python. I think in the past I have tried opening the feature data set first and then the feature class but didn't have any luck (getting some null pointers). Do you have any python samples for that? – crmackey Jun 12 '15 at 13:55
  • 1
    Not so much, I'm really only starting with comtypes in python, but as for opening a feature class from a workspace (IFeatueWorkspace) object just use the name, don't include the feature dataset at all - it doesn't matter if it's in a feature dataset, all names are unique... see help.arcgis.com/en/sdk/10.0/arcobjects_net/componenthelp/… Can you open a new question with some code and I'll have a look. The feature dataset can be used with an iteration of datasets (IFeatureDataset.Subsets) but it's cleaner to just open with the name. – Michael Stimson Jun 14 '15 at 22:08
  • 1
    Thanks @Michael Miles-Stimson. I will give it another shot. If I can't figure it out, I'll post a new question with my current code. – crmackey Jun 15 '15 at 13:14
  • @MichaelStimson I understand that I can use arcobjects in python using comtypes. I've never used arcobjects. Given that there aren't samples anywhere for performing tasks e.g build network dataset using comtypes and arcpy, do I need to first understand arcobjects before I can use comtypes? Or can I just learn comtypes by itself in order to use arcpy and comtypes? – ketar Aug 16 '17 at 14:47
  • 1
    @ketar, it's a good idea to know a little about ArcObjects before you try to use them in python. Although there aren't many samples of ArcObjects in python (yet) there are samples in the ArcObjects help like resources.arcgis.com/en/help/arcobjects-net/conceptualhelp/… for network datasets (build is the last item on that page). ArcObjects code is significantly more verbose than python (arcpy); personally I'd code in VB or C# and then when happy with the results copy/paste into python. – Michael Stimson Aug 16 '17 at 21:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.