Wednesday, June 29, 2011

Thursday, June 23, 2011

Creating a PDF Document

One of the most interesting features with the mapping module in python is the ability to create a PDF document.  This feature can be used as the starting point of developing tools that can create map books.

The PDFDocument allows manipulation of PDF documents, including facilities for merging pages, setting document open behavior, adding file attachments, and creating or changing document security settings.  PDFDocumentOpen and PDFDocumentCreate are two functions that provide a reference to a PDFDocument object.

Example: Creating a Blank PDF Document

pdfPath = r"c:\temp\my.pdf"
pdfDoc = arcpy.mapping.PDFDocumentCreate(pdfPath)

#Commit changes and delete variable reference
pdfDoc.saveAndClose()
del pdfDoc

*Always call saveAndClose(), or the pdf will not appear on the file system.

Example: Opening a PDF Document and update properties

import arcpy
pdfDoc = arcpy.mapping.PDFDocumentOpen(r"C:\Project\ParcelAtlasMapBook.pdf")
pdfDoc.updateDocProperties(pdf_title="Atlas Map",
                           pdf_author="ESRI",
                           pdf_subject="Map Book",
                           pdf_keywords="Atlas; Map Books",
                           pdf_open_view="USE_THUMBS",
                           pdf_layout="SINGLE_PAGE")
pdfDoc.saveAndClose()
del pdfDoc


The mapping module in ArcPay allows for a host of properties to be updated as in the example above.

More examples and a full explanation of the object can be found here.

Wednesday, June 22, 2011

ArcPy's PictureElement Object

The PictureElement object provides access to properties that enable its reposition on the page layout as well as changing the images source.  Like previous posts, to get a reference to the PictureElement using the ListLayoutElements() which produces a list of page layout element objects.


Example: Changing the Datasource of a Picture Element

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\temp\Project.mxd")
for elm in arcpy.mapping.ListLayoutElements(mxd, "PICTURE_ELEMENT"):
   if elm.name == "Logo":
      elm.sourceImage = r"C:\temp\NewLogo.bmp"
mxd.save()
del mxd

Friday, June 17, 2011

AddIn Extension - Update Document Properties

A way to keep track of who accesses map documents is to create an extension that logs who last used the map document.  To do this, you need to wire events to the OpenDocument event.


        private void WireDocumentEvents()
        {
            // Named event handler
            ArcMap.Events.OpenDocument += new IDocumentEvents_OpenDocumentEventHandler(Events_OpenDocument);
         
        }

Call this event OnStartup(), so override it in the extension:

        protected override void OnStartup()
        {
            // Wire the events
             WireDocumentEvents();
        }

Next stub out the Events_OpenDocument()


        void Events_OpenDocument()
        {
            IMxDocument mxDoc = ArcMap.Document as IMxDocument;
            IDocumentInfo2 docInfo = mxDoc as IDocumentInfo2;
            docInfo.Comments += Environment.NewLine + "Opened On: " + DateTime.Now.ToString() + " by " + Environment.UserName;
            if (docInfo.RelativePaths == false)
            {
                docInfo.RelativePaths = true;
            }
        }

Now if you look at the Map Documents properties in ArcMap you'll see something like:
"Opened On: 06/17/2011 3:02 AM by Bob1234"

An additional option would be to tack in a Save() to save the document right away so the information doesn't get lost if the user exits ArcMap.


        private void Save()
        {
            ArcMap.Application.SaveDocument();
        }

Thursday, June 16, 2011

Using the TextElement Object

The TextElement Object provide access to properties that allows developers move and alter the text within a map document.  It changes text within a page layout and can be used on inserted text, call outs, rectangle text, titles, etc...  It is even dynamic enough to access grouped text elements. 

To find the TextElement on the page layout use the ListLayoutElement() with a filter of TEXT_ELEMENT for the type to return. 


import arcpy
from arcpy import mapping
mxd = mapping.MapDocument("CURRENT")
textElem = mapping.ListLayoutElements(mxd, "TEXT_ELEMENT")


Now a list of all TextElements are returned. The next step is to find the text element you want to change. Each TextElement has a property called 'name' and when you perform your cartographic duties, I strongly suggest you set this. It makes it easy to update the elements.


import arcpy
from arcpy import mapping
mxd = mapping.MapDocument("CURRENT")
textElem = mapping.ListLayoutElements(mxd, "TEXT_ELEMENT")
for elem in textElem:
   if elem.name == "txtTitle":
      elem.text = "Mr. Toads Wild Ride"
   elif elem.name == "txtCurrentDate":
      # make an element have dynamic text of current date
      elem.text = """<dyn format="short" type="date">"""

mxd.save()
del mxd


Now you might be thinking dynamic text, what the heck is that!?!? well it's exactly what it means, it's dynamic, it changes, and it's awesome. You can check out more here.

Wednesday, June 15, 2011

Advanced - Extending Search Cursor Object

The 'with' statement is used to wrap the execution of a block with functionality provided by a separate guard object.  The expression is evaluated once, and should yield a context guard, which is used to control execution of the suite. The guard can provide execution-specific data, which is assigned to the target (or target list). 

As a developer, think of a 'with' statement as a try/finally pattern

def opening(filename):
   f = open(filename)
   try:
      yield f
   finally:
      f.close()

This can now be viewed as:

with f = opening(filename):
   #...read data from f...

This makes writing code easier to understand, and you don't have to always remember to delete the object reference using the del().

How does this apply to the Cursor object? Well natively, the Cursor object does not support 'with' statement use. To extend the Cursor, more specifically SearchCursor, first create a class:

class custom_cursor(object):
   """
      Extension class of cursor to enable use of
      with statement
   """
   def __init__(self, cursor):
      self.cursor = cursor
   def __enter__(self):
      return self.cursor
   def __exit__(self, type, value, traceback):
      del self.cursor

Now you have an object called custom_cursor that has the minimum required class properties of __enter__() and __exit__(). Notice that __exit__() performs that annoying del() to remove the schema locks on our data.

How do you use this? It's not hard at all.  In this example, the function searchCursor() takes a feature class or table and a where clause (expression) and returns the results as an array of Row objects. 

def searchCursor(fc,expression=""):
   """
       Returns a collections of rows as an array of Row Objects
      :param fc: Feature Class or Table
      :param expression: Where Clause Statement (Optional)
      :rtype Rows: Array of Rows
  
   """
   rows = []
   with custom_cursor(arcpy.SearchCursor(fc,expression)) as cur:
      for row in cur:
         rows.append(row)
   return rows


The del() is taken care of automatically, and when the process is completed the __exit__() is called launching the del().

I got this idea from Sean Gillies Blog

Tuesday, June 14, 2011

The Legend Element

Continuing with the mapping module objects, leads me to start talking about cartography automation.  The LegendElement object allows developers to access properties that enables the altering of the legend on the page layout.  The LegendElement object has an association with a single data frame. 

Example: Add Layer to Legend

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\temp\BestMapEver.mxd")
df = arcpy.mapping.ListDataFrames(mxd)[0]
addLayer = r"c:\temp\World.lyr"
# Get the First Legend Object
#
legend = arcpy.mapping.ListLayoutElements(mxd, "LEGEND_ELEMENT", "Legend")[0]
legend.autoAdd = True
arcpy.mapping.AddLayer(df,addLayer,"BOTTOM")
mxd.save() # Now the map has the new layer which makes the map even better
del mxd


It's not too hard to do simple changing to map documents. All you need is a nice template to work off of, and you can alter the text, position and size of most map elements.

Monday, June 13, 2011

Working with NetCDF Data

What is a NetCDF file? It stands for Network Common Data Form (NetCDF). It's binary, self-describing, machine independent file format for storing scientific data, and ArcGIS supports this format!

To get the properties use the NetCDFFileProperties() and as a developer you can access the following Methods:



Some essential NetCDF vocabulary: (source)
Dimensions
A netCDF dimension has both a name and a size. A dimension size is an arbitrary positive integer. Only one dimension in a netCDF file can have the size UNLIMITED. Such a dimension is the unlimited dimension, or the record dimension. A variable with an unlimited dimension can grow to any length along that dimension.

A dimension can be used to represent a real physical dimension, for example, time, latitude, longitude, or height. A dimension can also be used to index other quantities, for example, station or model run number. It is possible to use the same dimension more than once in specifying a variable shape.

Variables
A variable represents an array of values of the same type. Variables are used to store the bulk of the data in a netCDF file. A variable has a name, a data type, and a shape described by its list of dimensions specified when the variable is created. The number of dimensions is called the rank (or dimensionality). A scalar variable has rank 0, a vector has rank 1, and a matrix has rank 2. A variable can also have associated attributes that can be added, deleted, or changed after the variable is created.

Coordinate variables
A one-dimensional variable with the same name as a dimension is called a coordinate variable. It is associated with a dimension of one or more data variables and typically defines a physical coordinate corresponding to that dimension.

Coordinate variables have no special meaning to the netCDF library. However, the software using this library should treat coordinate variables in a special way.

Attributes
NetCDF attributes are used to store ancillary data or metadata. Most attributes provide information about a specific variable. These attributes are identified by the name of the variable together with the name of the attribute.

Some attributes provide information about the entire netCDF file and are called global attributes. These attributes are identified by the attribute name together with a blank variable name (in CDL) or a special null variable ID (in C or Fortran).

Conventions
The conventions define metadata that provide a definitive description of the data in each variable and their spatial and temporal properties. A convention helps users of data from different sources decide which quantities are comparable. The name of the convention is presented as a global attribute in a netCDF file.


Example: Print out all Dimension Values

import arcpy

InNetCDF = r"C:\temp\tos_O1_2001-2002.nc"
try:
   ncFP = arcpy.NetCDFFileProperties(InNetCDF)
   ncDim = ncFP.getDimensions()
   # loop through all dimension and show the value
   for dim in ncDim:
      top = ncFP.getDimensionSize(dim)
      for i in range(0,top):
         print ncFP.getDimensionValue(dim,i)
except:
   print arcpy.GetMessages(2)


In addition to just listing the properties, ArcMap/Catalog has a whole set of multidimensional tools that can convert this data into feature classes, raster layers or table views.

A tutorial on NetCDF and ArcGIS can be found here.

You can grab some NOAA data here.

Enjoy

Friday, June 10, 2011

The DataFrame Object

The DataFrame object provides access to many of the data frame properties found in a map document.  This object is essentially a gateway to use other mapping functions.  To access the data frame, use the ListDataFrames(), which returns a list.  Iterate through the list to find the correct DataFrame object.  This object allows developers to set the credits, map description, extent, see the map units, set the data frames name, position on page layout, width/height on page layout, and many other.  The complete overview can be found here

Example: Zoom to Selected Features From

mxd = arcpy.mapping.MapDocument("CURRENT")
df = arcpy.mapping.ListDataFrames(mxd)[0]
df.zoomToSelectedFeatures()
del mxd

This example assumes you have layers already selected in the current map document.

Example: Changing Data Frame's Size and Position

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\temp\Project.mxd")
df = arcpy.mapping.ListDataFrames(mxd)[0]
df.elementPositionX, df.elementPositionY = 1, 1
df.elementHeight, df.elementWidth = 5, 6.5
mxd.save()
del mxd


Example: Spatial Query by Visible Extent

mxd = arcpy.mapping.MapDocument("CURRENT")
df = arcpy.mapping.ListDataFrames(mxd)[0]
layers = arcpy.mapping.ListLayers(mxd, "", df)
# Extent Polygon
extentPolygon = arcpy.Polygon(arcpy.Array([df.extent.lowerLeft,df.extent.lowerRight, df.extent.upperRight, df.extent.upperLeft]),
df.spatialReference)
# Select all features in current extent
for lyr in layers:
   arcpy.SelectLayerByLocation_management(lyr, "INTERSECT", extentPolygon, "", "NEW_SELECTION")
del mxd


Enjoy

Thursday, June 9, 2011

Exporting a Map Document to PDF

So you made a map, and you want to show it to everyone without printing it, well let's export it!
ExportToPDF() - exports the page layout or data frame of a map document to a PDF.

From Esri WebHelp:
PDF files are designed to be consistently viewable and printable across different platforms. They are commonly used for distributing documents on the Web and are becoming a standard interchange format for content delivery. ArcMap PDFs are editable in many graphics applications and retain annotation, labeling, and attribute data for map layers from the ArcMap table of contents. PDF exports from ArcMap support embedding of fonts and thus can display symbology correctly even if the user does not have ESRI fonts installed. PDF exports from ArcMap can define colors in CMYK or RGB values.

Example: Export Page Layout

import arcpy
ds = r"c:\temp\ds.mxd"
mxd = arcpy.mapping.MapDocument(ds)
arcpy.mapping.ExportToPDF(mxd, r"C:\temp\layout.pdf")
del mxd

Example: Export a Single Data Frame

import arcpy
mxd = arcpy.mapping.MapDocument(r"C:\temp\layout.mxd")
df = arcpy.mapping.ListDataFrames(mxd)[0]
arcpy.mapping.ExportToPDF(mxd, r"C:\Project\Output\ProjectDataFrame.pdf", df,
                          df_export_width=1600,
                          df_export_height=1200)
del mxd


You can find more information about ExportToPDF() here.

Wednesday, June 8, 2011

Introduction to Mapping Module

The arcpy.mapping module is a library that allows developers to open and alter ArcMap map documents (mxd) and layer files (lyr).

Example: Update Text Element in Map Document

mxd = arcpy.mapping.MapDocument(r"C:\GIS\TownCenter_2009.mxd")
for textElement in arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT"):
   if textElement.text == "GIS Services Division 2009":
      textElement.text = "GIS Services Division 2010"
arcpy.mapping.ExportToPDF(mxd, r"C:\GIS\TownCenterUpdate_2010.pdf")
del mxd


The geoprocessing arcpy.mapping module is intended for use by anyone who needs to automate an ArcMap work flow.

Tuesday, June 7, 2011

The Polyline Object

A Polyline object is a shape defined by one or more paths, in which a path is a series of connected segments.



Method information can be found here.


Example: Creating a Polyline

import arcpy
coordList = [[[1,2], [2,4], [3,7]],
            [[6,8], [5,7], [7,2], [9,5]]]
# Create empty Point and Array objects
#
point = arcpy.Point()
array = arcpy.Array()

# A list that will hold each of the Polyline objects
#
featureList = []

for feature in coordList:
# For each coordinate pair, set the x,y properties and add to the
# Array object.
#
for coordPair in feature:
   point.X = coordPair[0]
   point.Y = coordPair[1]
   array.add(point)
   # Create a Polyline object based on the array of points
   #
   polyline = arcpy.Polyline(array)
   # Clear the array for future use
   #
   array.removeAll()
   # Append to the list of Polyline objects
   #
   featureList.append(polyline)

# Create a copy of the Polyline objects, by using featureList as input to
# the CopyFeatures tool.
#
arcpy.CopyFeatures_management(featureList, "c:/geometry/polylines.shp")

Monday, June 6, 2011

Python Tools for Visual Studios

Program in python, but miss that MS Visual Studios feel?  Yes?  Well I have the answer for you here.

PyTools is:
Python Tools for Visual Studio is a free & open source plug-in for Visual Studio 2010 from Microsoft's Technical Computing Group. PTVS enables developers to use all the major productivity features of Visual Studio to build Python code using either CPython or IronPython and adds new features such as using High Performance Computing clusters to scale your code. Together with one of the standard distros, you can turn Visual Studio into a powerful Technical Computing IDE...



Want it to work with ArcPy? Check this out.

Polygon Objects

The Polygon object is simply a collection of X,Y coordinates that form a closed shape in a connected sequence.

To create a polygon object, you need a List or Array of Points passed during instantiation at minimum.  There are 3 optional parameters: spatial reference, hasZ and hasM.  Here is more detailed explaination below:

This screen shot from the help in ArcGIS explains in details what each property and syntax means.  I do not need to really go into this because I assume everyone can read. The methods overview can be found here.

Example: Creating a Polygon Object From a List of Coordinates

import arcpy

# Triangle Coordinate List
#
cList = [[1,2],[2,2], [1,4]]

# Create a Point Object and Array
#
point = arcpy.Point()
array = arcpy.Array()

for feat in cList:
   point.X = feat[0]
   point.Y = feat[1]
   array.add(point)
   point = arcpy.Point()

# Close the shape
#
array.add(array.getObject(0))

# Create the polygon
#
polygon = arcpy.Polygon(array)

## DO OTHER TASKS ##

Friday, June 3, 2011

Geometry to GeoJSON

Working off of yesterday's post, the goal is converting the Geometry Objects back to GeoJSON. The geometry object has a method called __geo_interface__. It will work with Geometry, Multipoint, PointGeometry, Polygon, or Polyline classes.  It is not implemented on the Point object.

Example: Convert all features in a feature class to GeoJSON


import arcpy
from arcpy import env
import os
 

wrksp = r"c:\temp"
rows = None
row = None


env.workspace = wrksp


for ds in arcpy.ListFeatureClasses():
   print os.path.basename(ds)
   rows = arcpy.SearchCursor(ds)
   for row in rows:
      print row.getValue("Shape").__geo_interface__
   del rows, row


It's that simple to convert geometry to GeoJSON.

Thursday, June 2, 2011

GeoJSON to Geometry

Arcpy contains a helpful tool to convert GeoJSON objects to Geometry objects.  GeoJSON is a text representation of geographic features.  To convert GeoJSON to geometry use the AsShape(). This function can create the following geometries: Point, LineString, Polygon, MultiPoint, and MultiLineString.

GeoJSON Point to Point Geometry

import arcpy
gjPoint = {"type": "Point", "coordinates": [5.0, 5.0]}
ptGeometry = arcpy.AsShape(ptGeometry)


GeoJSON Multipoint to Multipoint Geometry

import arcpy
gjMultiPoint = {
"type": "MultiPoint",
"coordinates": [[5.0, 4.0], [8.0, 7.0]]}
multiPoint = arcpy.AsShape(gjMultiPoint)


GeoJSON Polyline to Polyline Geometry

import arcpy
gjLineString = {
"type": "LineString",
"coordinates": [[5.0, 4.0], [8.0, 7.0]]}
polyLine = arcpy.AsShape(gjLineString)


GeoJSON Polygon to Polygon Geometry

import arcpy
gjPolygon = {
"type": "Polygon",
"coordinates": [
[[10.0, 0.0], [20.0, 0.0], [20.0, 10.0], [10.0, 10.0], [10.0, 0.0]]]}
polygon = arcpy.AsShape(gjPolygon)


(sample code came from webhelp.esri.com)

Enjoy

Wednesday, June 1, 2011

Determining Error Types in Python

Many times when writing python code, you want to get some error feedback.  Python, like many other languages, has a try/catch or in this case a try/except/finally pattern where you can capture various errors:

Example: Simple Try/Except Pattern

try:
   a = 1/0
except:
   print 'error'
finally:
   print 'i'm done!'

The code will throw an error and go the to the except section of the code skipping everything else, but not much is being told to the developer.
Let's try to add a trace function that will capture the line where the error occurs and some other information:

import arcpy

def trace(self):
   import sys, traceback, inspect
   tb = sys.exc_info()[2]
   tbinfo = traceback.format_tb(tb)[0]
   # script name + line number
   line = tbinfo.split(", ")[1]
   filename = inspect.getfile( inspect.currentframe() )
   # Get Python syntax error
   #
   synerror = traceback.format_exc().splitlines()[-1]
   return line, filename, synerror


try:
   a=1/0
except:
   line, filename, err = trace()
   print "Python error on " + line + " of " + filename + " : with error - " + err
   arcpy.AddError("Python error on " + line + " of " + filename + " : with error - " + err)


Now we know more about the error than just 'error.' You can further expand your error handling by capturing arcpy errors verse everything else:

import arcpy

def trace(self):
   import sys, traceback, inspect
   tb = sys.exc_info()[2]
   tbinfo = traceback.format_tb(tb)[0]
   # script name + line number
   line = tbinfo.split(", ")[1]
   filename = inspect.getfile( inspect.currentframe() )
   # Get Python syntax error
   #
   synerror = traceback.format_exc().splitlines()[-1]
   return line, filename, synerror


try:
   a=1/0
except arcpy.ExecuteError:
   line, filename, err = trace()
   print "Geoprocessing error on " + line + " of " + filename + " :"
   arcpy.AddError("Geoprocessing error on " + line + " of " + filename + " :")
   arcpy.AddError(arcpy.GetMessages(2))
except:
   line, filename, err = trace()
   print "Python error on " + line + " of " + filename + " : with error - " + err
   arcpy.AddError("Python error on " + line + " of " + filename + " : with error - " + err)

Now you know if it's a python error or an arcpy error. Very cool and helpful

You can further break down error handling by creating custom error classes and use the raise method to call that class, but that's another time.