Friday, October 26, 2012

Creating a Dynamic Fishnet Tool - Python Addin

At 10.1, you can create python add-ins.  These add-ins are very much like add-ins in .NET, but they do not support GUI development.  This means that you can either just call a function after interacting with a map, or you can open an existing tool.

On the resources.arcgis.com page, one example given is the use of fishnet, but the grid sizes are static at 10x10.  This is a basic example,  much like the 'Hello World' examples in most programming languages, but what if you want user inputs into the fishnet tool?  Can it be done?  The answer is yes, but you need more add-in components to do it.

To use the code posted, you must first install the python add-in component for ArcGIS 10.1.  You can find that here.

After that is installed, create a new project, and add a toolbar, a tool, and two combo boxes.
When naming the ID and Class Name, do not use the same name.  This will lead to problem if you have to communicate between controls.
I called my controls the following:

  • toolbar - ID: analysisTB
  • combobox 1: ID: cboRows1, Class Name: cboRows
  • combobox 2: ID cboColumns1, Class Name: cboColumns
  • tool: ID: fishnetTool101 Class Name fishnetTool
The code behind will create multiple classes and events.  Erase the events that are not needed, so the control is not listening to events that are not needed. 


First code the combo box controls.  Here we will set the control values and store the values to a global variable rows and columns:


rows = 0
columns = 0
class cboClassColumns(object):
    """Implementation for fishnet_addin.cboColumns (ComboBox)"""
    def __init__(self):
        self.items = [i for i in xrange(1, 100)]
        self.editable = True
        self.enabled = True
        self.dropdownWidth = 'WWW'
        self.width = 'WWW'
    def onSelChange(self, selection):
        global columns
        columns = int(selection)
    def onEditChange(self, text):
        global columns
        try:
            columns = int(text)
        except:
            columns = 0
            pythonaddins.MessageBox("Please enter a valid integer", "Value Error")

class cboClassRow(object):
    """Implementation for fishnet_addin.cboRows (ComboBox)"""
    def __init__(self):
        self.items = [i for i in xrange(1, 100)]
        self.editable = True
        self.enabled = True
        self.dropdownWidth = 'WWW'
        self.width = 'WWW'
    def onSelChange(self, selection):
        global rows
        rows = int(selection)
    def onEditChange(self, text):
        global rows
        try:
            rows = int(text)
        except:
            rows = 0
            pythonaddins.MessageBox("Please enter a valid integer", "Value Error")

OnSelChange event sets the row or column value, and since I made my control editable, I validate the user's entry on the onEditChange event.

Next is the fishnet function.  This code goes behind button control in this example:


class fishnetClass(object):
    """Implementation for fishnet_addin.fishnetClassID (Tool)"""
    def __init__(self):
        self.enabled = True
        self.shape = "Rectangle"
        self.cursor = 3
    def onRectangle(self, rectangle_geometry):
        """ creates a temp fishnet polygon """
        extent = rectangle_geometry
        fishnet = None
        global rows
        global columns
        fshFC = r"in_memory\fishnet"
        if arcpy.Exists(fshFC):
            arcpy.Delete_management(fshFC)
        try:
            fishnet = arcpy.CreateFishnet_management(fshFC,
                            '%f %f' %(extent.XMin, extent.YMin),
                            '%f %f' %(extent.XMin, extent.YMax),
                            0, 0, int(rows), int(columns),
                            '%f %f' %(extent.XMax, extent.YMax),'NO_LABELS',
                            '%f %f %f %f' %(extent.XMin, extent.YMin, extent.XMax, extent.YMax),
                            'POLYGON')
            arcpy.RefreshActiveView()
            del extent
        except:
            pythonaddins.MessageBox("Cannot Run the Fishnet Tool", "Error Message")
        return fishnet

Run the makefile.py, and install the add in. Now you can create a dynamic fishnet tool using python.

Thursday, October 25, 2012

Using pythonaddins Messagebox

New 10.1, there is a module created for python add-ins.  It's called the pythonaddins module, and you can create a messagebox.  The messagebox object allows you to display messages to an end user.

The arcpy help can be found here.

So the messagebox can be defined as such:

MessageBox(message, title, {mb_type})

To use you just pass in a message and title as text.  The mb_type is the type of messagebox type value.  It supports the following types:

mb_type value
0
OK Only
1
OK/Cancel
2
Abort/Retry/Cancel
3
Yes/No/Cancel
4
Yes/No
5
Retry/Cancel
6
Cancel/Try Again/Continue


Complete Example:
>>> import pythonaddins
>>> result = pythonaddins.MessageBox("Press Cancel", "TITLE", 1)
>>> print result
Cancel


Simple Right!

Wednesday, October 24, 2012

Visual Studios 2010 - Configuring a 64-Bit C++ Project

I can say it better that Microsoft's help, so here is the link.

At 10.1 there will be support for 64-Bit Python Geoprocessing for the desktop software.  It's probably going to be a good idea to know how to do this because you'll now have to account for both 32 and 64 bit builds.

Enjoy


Tuesday, October 23, 2012

Easy C++ Project for Python Use

For this tutorial, I will be using Visual Studios 2010 (C++) and Python 2.7 with ctypes.

Let's assume your boss wants a program that performs addition, there are many ways to write this code, but for the sake of this this tutorial, you want to use the python and C++.
  • Open Visual Studios
  • Follow the steps in creating a project here.
  • Give the project a name called additioncpp
  • Create a header file called 'additioncpp.h' and enter in the following code:
      int sum(int, int);
  •  In the dllmain.cpp file, comment out everything but the '#include "stdafx.h" line
  • Now we are ready to write the sum() we defined in the additioncpp.h header file.  Since python like 'C', and not 'C++' we need to use exten "C" around the functions
      #include "stdafx.h"
      #define DLLEXPORT extern "C" __declspec(dllexport)

      DLLEXPORT int sum(int a, int b) {
          return a + b;
       }

  • Compile the code and copy the .dll to a new folder
  • Create a new .py file and type in the following:
  • print 'start' 
    from ctypes import cdll
    mydll = cdll.LoadLibrary(r'PATHTODLL\additioncpp.dll')
    print mydll
    print mydll.sum
    print mydll.sum(1,2)
    print 'end'
  • You should see an output like this:
>>> 
>>> <_funcptr 0x10ca47b0="0x10ca47b0" at="at" object="object">
>>> 3
>>> end

Enjoy

Monday, October 22, 2012

Exporting C++ Functions for Use in C


If you have functions in a dll written in C++ that you want to access from C, you should declare these functions with C linkage instead of C++ linkage. Unless otherwise specified, the C++ compiler uses C++ type-safe naming (also known as name decoration) and C++ calling conventions, which can be difficult to call from C.
To specify C linkage, specify extern "C" for your function declarations. For example:

   extern "C" __declspec( dllexport ) int MyFunc(long parm1);

 More help can be found here.

Friday, October 19, 2012

What is Extern "C" in C++?

If you are going to use ctypes in python, then you need to export your dll with a C-wrapper.  

You can find a great definition of extent "C" here.  In case you do not want to click on the link, here is what is said by 'Faisal Vali'.

extern "C" makes a function-name in C++ have 'C' linkage so that client C code can use your function using a 'C' compatible header file that contains just the declaration of your function. Your function definition is contained in a binary format that the client 'C' linker will then link to using the 'C' name.  Since C++ has overloading of function names and C does not, the C++ compiler cannot just use the function name as a unique id to link to, so it mangles the name by adding information about the arguments. A C compiler does not need to mangle the name since you can not overload function names in C. When you state that a function has extern "C" linkage in C++, the C++ compiler does not add argument/parameter type information to the name used for linkage.
I couldn't have said it better myself, so I won't


Thursday, October 18, 2012

Setting up C++ Project for Python/ArcGIS Use

This article will discuss setting up a Visual Studios 2010 project to create a DLL using C++ which can be called by python using ctypes.

 1. Open Visual Studios
 2. Create a new C++ Win32 Project
 3. Give it a name, example: simplepythoncpp
 4. Press next 
 5. Select ‘Dll’ for the type of project
 6. Here you’ll have a set of created header files (.h) and Source Files (.cpp)
 7. To use ArcObjects, you need to reference the com libraries.  To do this, you need to tell visual studios where these libraries are.  To set the location, do the follow:
      a. In the Solution explorer, right click and select properties
      b. Select ‘VC++ Directories’
      c. Click on ‘Library Directories’ 
      d. Select ‘Edit’
      e. Navigate to the ArcGIS 10.1 install directory and select the ‘com’ folder
 8. Staying in the properties, we need to enable ‘C++/CLI’ 
      a. Click on ‘Command Lin’
      b. Under ‘Additional Options’ type ‘/clr’
 9. Next we need to define some ‘Code Generation’ options
      a. Click on ‘Code Generation’
      b. Under ‘Basic Runtime Checks’ select ‘Default’
 10. Press ‘Apply’ button then ‘OK’
Let’s test our project changes.
 1. Open the ‘stdafx.h’ header file
 2. Leave the default #include statements, and type in the following:

#import "esriSystem.olb" raw_interfaces_only, raw_native_types, no_namespace, named_guids, exclude("OLE_COLOR", "OLE_HANDLE", "VARTYPE")

 3. Compile project
No errors should appear, so your project is setup correct.  
Where do you do from here? Well, all the libraries you need in this header file, and reference them in your .cpp file.  

Wednesday, October 17, 2012

c++ and python = woot!

Everything old is new, and so begin my venture back into the C++.  My focus now is using C++ libraries with ArcObjects, slightly different then what I did way back in the before times.  

Some tips:

  • Create a DLL project and ensure that the ArcGIS/com folder is mapped to the project properties -> 'VC++ Directories' -> 'Include Directories'
  • get comfortable with resources.arcgis.com and the Visual Studios help system
  • Experiment and push the limits
Enjoy the ride

Tuesday, October 9, 2012

ArcGIS 10.1 and matplotlib

Graphing at 10.1 is now easier because ArcGIS comes with matplotlib.  To me, this is an exciting enhancement that broadens what python developers can do when it comes to visualization of data in ArcGIS.

A simple example of this is the basic X/Y plot but instead of using random number, assume we have a table and the X/Y (distance/elevation) is in your table.

Example:

import arcpy
import os
import matplotlib.pyplot as plt
x = []
y = []

elevPNG = env.scratchFolder + os.sep + "elev.png"
fig = plt.figure()
table = r"C:\temp\scratch.gdb\data"
fields = ["FIRST_DIST", "FIRST_Z"]
with arcpy.da.SearchCursor(table, fields) as rows:
    for row in rows:
        x.append(row[0])
        y.append(row[1])
plt.plot(x,y, color='r')
plt.xlabel('Distance From Start Location')
plt.ylabel('Elevation')
plt.title('Landscape Profile')
fig.savefig(elevPNG, dpi=fig.dpi)


So what has happened, is that the x and y information is stored in a 1:1 fashion in list objects.  The figure is created and lists populated using a simple search cursor.  The line graph is shown using the plot() and labels are added to make the graph easier to read.

The results is something like this:
In this application, a user can quickly assess the terrain without examining each elevation point, and can determine if a better route is needed.


Friday, October 5, 2012

Archiving Made Easy at Python 2.7

At python 2.7, there is a great shutil tool called make_archive().  It can make either a .zip or .tar file, and eliminates the need to create a walking function to compress files.

The function supports the following compression types by default:
  • zip
  • bztar
  • gztar
  • tar
But if you have another format, you can register it using the register_archive_format().

Example:
compressFile = shutil.make_archive(r"c:\temp\mycompressfile", "zip", r"c:\temp\data")
Notice that the first parameter doesn't have the file extension.  The function will return the path to the newly created compressed file.

To uncompress the file, you will have to do that manually using zipfile or tarfile modules.

Enjoy