Vitals: ArcGIS-ArcView 10.0, Python 2.6.5, PythonWin, comtypes

I'll start with my main questions:

  • How, using ArcObjects, do I go about creating non-feature-linked geodatabase annotation features that have a simple line callout format, and
  • How do I set the anchor point so that the leader line remains functional, i.e. remains anchored on the point feature it references even when the annotation is moved?

I have a standalone Python script that accesses ArcObjects through comtypes wrappings of ArcGIS OLBs -- works great. Overall, this ArcMap automation script cursors through a point feature class, creating complex vector graphics (in map units in the data view) from attribute info in the points and loads each of those graphics (an IGroupElement) into a non-feature-linked annotation feature as the anno's graphic element. I want to have functional simple line callout leader lines, but the latter graphics-containing anno features won't support a simple line callout. So my idea is to create, for each of the graphic anno features, an underlying, transparent dummy text anno feature that will carry the leader line. I've tried a bunch of things, but I can't get a leader line to show up. I have defined separate annotation classes within the target annotation feature class for storage of graphic versus dummy text leader line anno features. The leader line anno class was defined with transparent text and a simple line callout symbology. Now, below are code excerpts that include the function I call to create each annotation feature, be it graphic or leader line dummy:

#make front leader text element
ldrText = '|'
pFLdrTextElement = BuildTextElement(ldrText, 'Leader', 'null', annoRefScale, False, (fX, fY))  #returns ITextElement
pFLdrTxtElem = CType(pFLdrTextElement, esriCarto.IElement)
pFLdrTxtElem.Geometry = pTweakedTxtPoint
ldrannoclass = 1
CreateAnnoFeature(annoFC, pFLdrTxtElem, fOID, ldrannoclass)

def BuildTextElement(s, signType, txtcolor, annoRefScale, has_outline, featPos=None):
    #separate out leader annotation text
    if signType == 'Leader':
        import comtypes.gen.esriGeometry as esriGeometry
        pTextColor = GetRGBColor(txtcolor)
        pSimpleLineCallout = NewObj(esriDisplay.SimpleLineCallout, esriDisplay.ISimpleLineCallout)
        anchorPoint = NewObj(esriGeometry.Point, esriGeometry.IPoint)
        anchorPoint.PutCoords(featPos[0], featPos[1])
        pSimpleLineCallout.AnchorPoint = anchorPoint
        pSimpleLineCallout.AutoSnap = True
        pLeaderLineSymbol = NewObj(esriDisplay.CartographicLineSymbol, esriDisplay.ILineSymbol)
        pLineColor = NewObj(esriDisplay.RgbColor, esriDisplay.IRgbColor)
        pLineColor.Red = 0
        pLineColor.Green = 0
        pLineColor.Blue = 0
        pLeaderColor = CType(pLineColor, esriDisplay.IColor)
        pLeaderLineSymbol.Color = pLeaderColor
        pSimpleLineCallout.LineSymbol = pLeaderLineSymbol
        pSimpleLineCallout.LeaderTolerance = 0.0
        pTextBackground = CType(pSimpleLineCallout, esriDisplay.ITextBackground)
        pTextSymbol.Background = pTextBackground
        pTextSymbol.Color = pTextColor
        pTextElement.Symbol = pTextSymbol
        pTextElement.Text = s
        pTextElement.ScaleText = True
        return pTextElement

def CreateAnnoFeature(pAnnoFC, pElement, featID, annoclass):
    """Creates new annotation feature, loads the graphic into it,
    and inserts the new anno feature into the anno feature class"""
    import comtypes.gen.esriGeoDatabase as esriGeoDatabase
    import comtypes.gen.esriCarto as esriCarto
    #make Insert cursor and FeatureBuffer for use in InsertFeature method
    pFCursor = pAnnoFC.Insert(True)
    pFeatBuffer = pAnnoFC.CreateFeatureBuffer()
    pFeature = CType(pFeatBuffer, esriGeoDatabase.IFeature)   
    #set annotation of anno feature
    pAnnotationFeature = CType(pFeature, esriCarto.IAnnotationFeature2)
    pAnnotationFeature.Annotation = pElement
    pAnnotationFeature.Status = esriCarto.esriAnnoStatusPlaced
    pAnnotationFeature.AnnotationClassID = annoclass
    pFeature = CType(pAnnotationFeature, esriGeoDatabase.IFeature)
    #insert the new anno feature
    pFeatOID = pFCursor.InsertFeature(CType(pFeature, esriGeoDatabase.IFeatureBuffer))
    mxDoc.ActiveView.PartialRefresh(esriCarto.esriViewGraphics, None, None)

The BuildTextElement() function contains the code to build either a plain text element or one with a simple line callout format including setting of the anchor point. (See code above for the latter.) The latter results in what looks like a simple line callout, but when the annotation is moved the leader line does not remain anchored on the point feature. The text and leader line are simply combined into a graphic that moves around as a whole with the anno. In the case of the plain text element, I was hoping that a leader line would show, because the simple line callout format is defined in the annotation class symbology.

In this case, how would I set the anchor point?

In fact, in either case the leader line dummy is created--the transparent text box is placed, but there is no leader line. I really don't have a good idea of whether I am going about this in the best way. I can't change the structure of looping through the point features one at a time, but how I go about building the annotation features can certainly change. For instance, using an IFDOGraphicsLayer and related objects instead of an Insert Cursor might work.

A related desire on my part is to be able to populate attributes of each anno feature right after (as?) I create it. Some of the attribute info will come from the point feature and some will capture the state of some of the properties of the anno feature. I really don't know how to go about this efficiently.

What are some ways I might do this?

Followup Question:

So, where in my code above can I get an ISymbolCollectionElement and on what?

I gather I should be making an empty text element with only a point location geometry assigned, passing this into CreateAnnoFeature(), and setting properties there through ISymbolCollectionElement. Because I create the anno feature as a FeatureBuffer from the anno feature class, the feature is an annotation feature; however, I don't think a feature supports ISymbolCollectionElement, and the TextElement I pass in is just a plain TextElement. It isn't the element of the anno feature until I set the Annotation property.

Can I get an ISymbolCollectionElement on the TextElement before I set it as the annotation element?

Assuming I can, I guess that all I would have to set is the text, the symbolID, and the anchor point. The leader line, text size, and transparency should be picked up through the symbology defined in the anno feature class.

On a related note, how do I know the SymbolID to assign?

In my existing anno class properties, the symbology tab shows the symbology that is defined for each of the annotation classes. When I create an anno feature and assign it to an annotation class, does it pick up the annotation class' symbology or do I need to assign the SymbolID explicitly (hence the first question above)? (Right now, I set the TextElement properties through ITextSymbol, so I imagine any link to the SymbolCollection that may normally exist is broken.)

  • You need to do the following: 1) have a symbol in the symbol collection with the properties you want and know the ID 2) Cocreate the TextElement and QI to ISymbolCollectionElement 3) on ISymbolCollectionElement::SharedSymbol with the symbol reference and the ID. This initializes the element with the symbol – Craig Williams Apr 15 '11 at 0:50
  • OK, so I guess I'm just not understanding the identity of the SymbolCollection. What are the symbols that I set for each annotation class when I created the anno feature class? Are they not in the SymbolCollection? Do I have to build a SymbolCollection for the feature class first? I am just confused. – celticflute Apr 15 '11 at 1:25
  • What exactly is the syntax of the SharedSymbol property? I have tried ISymbolCollectionElement.SharedSymbol(1, pSymbol) and get back TypeError: object is not callable. ISymbolCollectionElement.SharedSymbol(1) = pSymbol [VB6 syntax] gets me "can't assign to a function call." ISymbolCollectionElement.SharedSymbol = 1 gets me "attribute takes exactly 2 arguments (3 given)" The object diagrams show SharedSymbol (in SymbolID: Long): ISymbol as a property put. The latter doesn't make sense to me for a 2 argument property. So, any insights? (Python of course might be the problem.) – celticflute Apr 15 '11 at 4:49
  • 1
    ISymbolCollectionElement.SharedSymbol(1) = pSymbol see resources.esri.com/help/9.3/ArcGISDesktop/com/samples/… for an example in VBA – Craig Williams Apr 15 '11 at 5:01
  • The latter syntax, which assigns to a function call, is not allowed in Python. I think it is interesting that the .NET syntax, ISymbolCollectionElement.SharedSymbol(arg1, arg2) has both arguments passed ByVal. The pSymbol is a pointer object. Does this create a mismatch with ByVal? (I'm just reaching now.) – celticflute Apr 15 '11 at 14:45

This should be working. When you edit are you using the regular edit tool or the edit annotation tool? The anchor point only sticks with the edit annotation tool.

To optimize your code, I recommend using a symbol from the symbol collection and setting all properties via ISymbolCollectionElement. See http://help.arcgis.com/en/sdk/10.0/arcobjects_net/componenthelp/index.html#//001200000qzm000000 for more details.

  • Craig, it seems you were correct in your suspicion about the choice of edit tool. I could swear I was using the anno edit tool before, but apparently not. Additionally, I have edited my original question to add some followup questions regarding your recommendation for using the ISymbolCollectionElement for setting anno properties. That is right on the money. – celticflute Apr 14 '11 at 15:57
  • FYI, I have posted a separate question having bearing on the annotation layering concerned in this question. – celticflute Apr 14 '11 at 16:38

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