Access ArcMap UI from Python comtypes

I have blogged earlier about the possibility to submit calls to methods defined in classes stored within a .NET library either using clr (with the help of pythonnet) or using native ctypes depending on how your libraries have been compiled. I have also shared some of the examples on how one can use comtypes to access COM-objects of ArcGIS Desktop to be able interact with ArcObjects via Python without writing any C#, Java, or C++ code.

This time, I need combine these approaches. I have to execute a tool on a toolbar in ArcMap (this can be a custom .NET add-in or a built-in tool such as Full Extent) from Python code while working in an open ArcMap session. This is different from what I’ve done before because I have always worked with comtypes in a stand-alone mode accessing existing map documents stored on the disk. This time, I have to use comtypes within an ArcMap session hooking into the currently open map document.

Fortunately, this wasn’t as hard as I expected. I just had to use the standard interface to get into the application, and then into the map document, and then into the command bars available. The last thing you would need to do is to find the tool you need and then execute it. I’ve published a snippet that would get you started.

The first one is for calling the Full Extent button on the Tools toolbar in ArcMap. The second one is for calling a button on a custom toolbar that is part of a custom .NET add-in in ArcMap. You can both of these samples as a part of a custom Python script tool or a Python add-in.

 

Advertisements

Implementing Copy Parallel with ArcObjects

In ArcMap, in the Editor toolbar, you can find a command Copy Parallel which  will create a copy of a selected line feature at an offset distance you specify. This command is unique to the Editor toolbar and is not available as a geoprocessing tool. This makes it impossible to create parallel lines in batch (say, create lines to the both sides of the selected line feature starting with 10 meters up to 100 meters with the step of 20 meters).

A neat thing that exists in ArcMap is that you can assign a shortcut key to any command you can find in the Customize menu > Commands tab window. So, your workflow could look like this: you start the editing session, select a feature, use your shortcut key (e.g., Ctrl-Alt-P) and enter the offset value in the Copy Parallel window that appears. However, this implies that you would need to type an offset value for every copy to be created and you might also need to change other options in the window.

It’s important to understand that creating a parallel copy of a feature in the context of ArcGIS is not the same as creating a feature with a specified offset (because I thought it was). Compare the features produced using the Copy Parallel command (to the left) and using Python code (that just moves each vertex of a feature using an offset value) (to the right):

parallel

As you can see, the length of line segments can change when using the Copy Parallel command where as just moving vertices with offset preserves length of the segments.

You can call the Copy Parallel command using ArcObjects (just like any other command) but you won’t be able to supply any input parameters such as the offset value. So, it’s no different from assigning a shortcut key. The only solution left is to implement an own version of Copy Parallel that will mimic the built-in command. I have written an ArcGIS .NET add-in that can be called from a custom toolbar.

One can specify the intervals in a text box and then click the button.

parallelpara1

This is the code for the combo box item and for the button item:

 

 

Modify properties of scale bar element in ArcMap map document layout using Python and ArcObjects

This is yet another post on how to use ArcObjects and Python comtypes package to call ArcObjects COM objects using Python. In this example, we have a map document with a couple of layout elements pre-created such as a scale bar, a north arrow, and a scale text. We would like to modify the scale bar label. Currently, it is ‘Miles’, but we have decided that we would like to change this label to be ‘Miles (1.6 km)’ for all our map documents we are going to export to pdf later on.

arcpy site-package doesn’t provide a way to either read or write this information when accessing a MapsurroundElement object. This means we have to use ArcObjects. Keep in mind that through ArcObjects, you can read/write many other properties of the scale bar such as divisions, symbols, text formatting and many others.
Here is the code that will update scale bar unit label:

Access to ArcMap map document grid using Python and ArcObjects

When automating mapping production and exporting your map document layouts, you may need to manage map grids. They are grids or graticules which show the network of latitude and longitude lines in the layout.

For the very least, you would like to hide/show map grids in a map document’s data frame before exporting the map. The reason for this is that if your grid cell is of 1×1 km size, when exporting the map layout being in a small extent (for instance, 1: 1,000,000 you will get a pretty much no map except a black image because the grid will take all the place.

Being able to access map grids can also be handy when you have multiple grids with different grid cell sizes and you want to be able to control at what map scales each of the map grids should be visible.

Unfortunately, you cannot use arcpy to access grids defined for a data frame. However, the grids are exposed via an ArcGIS extension called Production Mapping which according to the Esri Help page

streamlines your GIS data and map production by providing tools that facilitate data creation, maintenance, and validation, as well as tools for producing high-quality cartographic products.

You might be surprised to learn this, but Production Mapping extension has its own arcpy-like site-package called arcpyproduction. Within this package, there is a class Grid class which provides access to grid properties for a grids and graticules layer. However, if you don’t have Production Mapping extension (it might be hard to justify buying this extension if you are not involved in heavy map production), as map grids are not exposed via arcpy, you have to use ArcObjects.

Since many would like to automate showing/hiding map grids when exporting map layouts, I’ve written a tiny function which can do that using Python. I have blogged earlier about how to start using ArcObjects from Python, so make sure to read this post first. Using this code, you will be able to choose which map grid you would like to use (if you have multiple grids with different cell size defined) when exporting a map layout.

Update field aliases in map document layers

If you have been using arcpy for a while, you might have found that there are certain limitations. For instance, you cannot:

  • create a new map document;
  • create/modify map document layout elements (for instance, change scale bar units or labels);
  • create map grid /graticules in layout;
  • edit network dataset properties;

Another fairly basic thing that you cannot do is to view/edit field aliases for layers in a map document. This is a quite common scenario when you have authored a map document using a feature class from a geodatabase. By default, the field aliases for the map layers you create are generated using field aliases of the feature class fields stored within the geodatabase. However, as soon as you’ve created a map layer from a feature class, the layer fields have no connection to their data source fields aliases.

This means that if you would change the alias of a feature class field, this won’t be reflected in the layer properties that refer to the feature class. Of course, you can always manually update the field aliases using Layer Properties window in ArcMap, however this can get complicated if you are not sure what fields should be updated.

This is because it is currently (ArcGIS 10.4.1) impossible to list the field aliases for a map layer. When you are using arcpy.ListFields() function, you won’t be able to get aliases for the fields that you can see from the Layer Properties window. This is because this function will pull the field aliases from the layer’s data source, that is a feature class.

This implies that if you have a bunch of map documents and you would want to update the map layers field aliases for them in bulk, you cannot use arcpy. You have two options really:

  1. Use X-Ray for ArcMap 10.1 add-in. Then you can open every map document file you want to apply changes to and use the command on the add-in’s toolbar.
  2. Use ArcObjects to access the map document file programmatically, make required changes in the layer field properties and save it.

I think it would be great if we could make those changes using arcpy. A common use case is when you have a suite of ArcGIS Server map services you maintain and in a case when a geodatabase feature class field alias is changed, you would like to reflect it in the service as well. So, you would need to update the field aliases for the map documents and then republish them. Obviously, you would like to script this.

Unless you would like to build a custom tool in .NET/Java, you would call comtypes package from Python to invoke ArcObjects COM methods. I have blogged about using comtypes for accessing ArcObjects from Python before, so be sure to check this post.

Here is a snippet of code I use for automating field aliases updates. I hope this can save you some time.

 

 

Accessing ArcObjects in Python

If you have used arcpy in ArcGIS for some time, you might have noticed that not all of those operations that are accessible to you via the user interface in ArcMap are exposed as functions and methods in arcpy.

From the Esri help:

It was not designed to be a complete replacement for ArcObjects or an attempt at creating a function, method, or property for every conceivable button, dialog box, menu choice, or context item in the ArcMap interface (that is what ArcObjects provides).

So, there are no plans to make the whole ArcGIS platform available via arcpy which is why it is sometimes referred to as coarse-grained API into ArcGIS. For those situations when you need to have a finer control over the GIS data management and maintenance, Esri recommends using ArcObjects. It is usually related to advanced data management operations where support in arcpy is very limited such as LIDAR data management, network dataset generation, write-access to the properties of workspaces and data repositories, or metadata management. All of this can be done in ArcObjects, and hence, the name – fine-grained API into ArcGIS.

Here are just a few of the things you cannot do in arcpy.

  • You cannot create a new network dataset with arcpy and neither do you have any geoprocessing tools for that; this can be done solely by using the New Network Dataset wizard.
  • You cannot create a new empty ArcMap map document with arcpy. This means if your workflows rely on generating map documents and adding layers into it, you need to pre-create an empty map document which will be used as a template.
  • You cannot create new bookmarks in a map document and neither can you import existing bookmarks into a map document. This can be done manually from the ArcMap user interface only.

However, if you do need to automate some of those workflows, either for one-time job when you need to process some data really quickly or when building a script that will be run on a regular basis, the only option you have is to use ArcObjects.

Learning ArcObjects can be hard due to its complexity. You would also need to learn Java or .NET (C# or VB) if you want to write ArcGIS add-ins or develop stand-alone applications. If you are not comfortable with those languages and have most of the workflows written in Python, I have good news for you. It is possible to access ArcObjects from Python.

This means that you can write your Python code using arcpy and other packages and incorporate some of the ArcObjects-based operations right into your code. This comes very handy when you lack just some minor operations in arcpy and need to use ArcObjects without getting out of your existing Python code.

To get started, please review this GIS.SE post: How do I access ArcObjects from Python? It has enough information to let you set up everything needed.

Then follow these steps:

  1. Install comtypes package (I recommend using pip, see How to install pip on Windows? on how to get it)
  2. Download the snippets.py file to get examples and some helper functions.
  3. Change the “10.2” in the snippets file to “10.3” and installation paths of ArcGIS accordingly.
  4. You are ready to access the ArcObjects from your Python code! Look here for a sample that will create a new map document.

There is no need to install the ArcObjects SDK; the only thing you need to have installed is ArcGIS Desktop.

You will need to play around with ArcObjects reference to find out what assembly and what interface you need to import. Here is the place you can start exploring the object model diagrams. Here is the section I recommend reviewing, Learning ArcObjects. Skip those parts you find irrelevant for you, though, as it covers nearly all ArcGIS Desktop operations.

In the API Reference part of the Help, you will find detailed information about the namespaces used in ArcObjects. Reading through this and visiting this often is an excellent way to lean ArcObjects. Here is an example of the Carto namespace.

Even though you do not need to know C#, VB, or Java, it is still worth to be able to read the code, as there are tons of useful snippets and code samples available in the Help system. Those will help you find out what interface should be used, any data type casting needed, and many more.

To learn more about the ArcObjects, listen to a recorded live-training seminar from Esri. Please share in comments what kind of operations you are missing in arcpy and would need to use ArcObjects to implement them.