Wednesday, May 23, 2007

Automating ArcMap

At some point between the release of ArcMap 8.0 and now, automated MXD creation/editing became much easier with ArcMap. Previously, a stand-alone application had to open up a session of ArcMap and then control it remotely through a poorly documented and buggy automation-like process. This wasn't true automation in the MS Office sense, but it was very similar. Unfortunately, it was very complicated to do properly and suffered from inexplicable speed problems - drawing a simple 8 point polygon on a map could take up to 20 minutes!

But now thanks to the IMapDocument interface, it's very easy to create a standalone application that can create a new MXD file or alter an existing one. Pretty much any object an ArcMap document can be accessed - data layers, page layout, graphic elements - you name it. It's still not true automation since you're not actually opening an ArcMap session, but if you really needed to, you could launch your MXD document once the stand-alone program does its job.

How easy is it? Here's a very simple sample that draws a big square on an existing MXD document and then saves it with a different name. From this starting point, you could also add layers, text and whatever crazy stuff you'd want on a map.


SimpleMXDChanger.zip Visual Studio Project


Imports ESRI.ArcGIS.Carto
Imports ESRI.ArcGIS.Geometry

Public Class Form1
Private m_pDoc As IMapDocument

Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click

CreateMXD()
AddRectangle()
SaveDoc()
MessageBox.Show("Done")
m_pDoc = Nothing
Me.Close()
End Sub

Private Sub
CreateMXD()
Dim aDoc As IMapDocument = Nothing
'Create a new MXD Document
Try
While aDoc Is Nothing
aDoc = New MapDocument
End While
Catch ex As Exception
MessageBox.Show(ex.Message, "Error")
End Try
aDoc.Open(Me.tbxOpen.Text)
m_pDoc = aDoc
End Sub

Private Sub
AddRectangle()
'Adds a graphic element to the layout
Dim pPageLayout As IPageLayout
Dim pContainer As IGraphicsContainer
Dim pGraphicElement As IElement
Dim pEnvelope As IEnvelope
'Specify where to put the rectangle (on the page layout)
pPageLayout = m_pDoc.PageLayout
pContainer = pPageLayout
'Create a rectangle

'If you wanted to get fancy, you could change the symbology here
pGraphicElement = New RectangleElement
pEnvelope = New Envelope
pEnvelope.XMax = 8
pEnvelope.XMin = 0.5
pEnvelope.YMax = 8
pEnvelope.YMin = 0.5
pGraphicElement.Geometry = pEnvelope

'Add the rectangle to the page layout
pContainer.AddElement(pGraphicElement, 0)
End Sub

Private Sub
SaveDoc()
'Save the MXD document
m_pDoc.SaveAs(Me.tbxSaveAs.Text)
End Sub

End Class

Monday, May 21, 2007

A better raster clipping and mosaic tool for ArcMap



I've been frustrated with processing raster imagery in ArcMap ever since 8.0 came out way back when. I finally got around to making my own tool that makes clipping and mosaicking much easier and more intuitive. All the relevant info (mosaic type, extent, etc) is right there on the dialog, and most importantly, this tool works seamlessly with raster catalogs. Another nicety is that the output is a plain old TIF file with a TFW file, so that AutoCad puts the image into the right place (for some reason I could never get ArcMap to make a GeoTIFF file that AutoCad Map could place properly).
The only thing I wasn't able to work out satisfactorily is setting the cell size. I wasn't able to have ArcMap do this while performing the mosaic, it would have to be a totally seperate step, which ended up being too time consuming.


Here's the full Visual Studio solution, including an installer:
WSSI_ArcMapTools1.zip

Here's some of the code for anyone that's interested in the inner workings. This is the section that performs the actual mosaic:



private IDataset Mosaic(IRasterCollection pRasterCollection, string strPath, string strName, IEnvelope pEnvelope, double CellSize, string MosaicType)
{
IMosaicRaster pMosaicRaster;
ISaveAs2 pSaveAs;
IDataset pResultData;
IWorkspaceFactory pWkspFact;
IWorkspace pWorkspace;
IRasterProps pRasterProps;

//Set mosaic type
pMosaicRaster = (IMosaicRaster)pRasterCollection;
switch (MosaicType)
{
case "Maximum":
pMosaicRaster.MosaicOperatorType = rstMosaicOperatorType.MT_MAX;
break;
case "Minimum":
pMosaicRaster.MosaicOperatorType = rstMosaicOperatorType.MT_MIN;
break;
case "Mean":
pMosaicRaster.MosaicOperatorType = rstMosaicOperatorType.MT_MEAN;
break;
case "Blend":
pMosaicRaster.MosaicOperatorType = rstMosaicOperatorType.MT_BLEND;
break;
case "First":
pMosaicRaster.MosaicOperatorType = rstMosaicOperatorType.MT_FIRST;
break;
case "Last":
pMosaicRaster.MosaicOperatorType = rstMosaicOperatorType.MT_LAST;
break;
default:
pMosaicRaster.MosaicOperatorType = rstMosaicOperatorType.MT_MAX;
break;
}

//Set the extent
pRasterProps = (IRasterProps)pMosaicRaster;
pRasterProps.Extent = pEnvelope;
pRasterProps.SpatialReference = m_MxDocument.FocusMap.SpatialReference;

//Set Cell Size...
// ... nevermind, we can't :(

//Set the save as
pWkspFact = new RasterWorkspaceFactoryClass();
pWorkspace = pWkspFact.OpenFromFile(strPath, 0);
pSaveAs = (ISaveAs2)pMosaicRaster;
IRasterStorageDef pRasterDef = new RasterStorageDefClass();
IPnt pPnt = new PntClass();
pPnt.X = CellSize;
pPnt.Y = CellSize;
pRasterDef.CellSize = pPnt; //This actually only works when storing an SDE Raster

pResultData = (IDataset)pSaveAs.SaveAsRasterDataset(strName, pWorkspace, "TIFF", pRasterDef);
return pResultData;
}