Multithreaded raster subset
SubsetRasterTool.cs
// Copyright 2006 ESRI
//
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
//
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
//
// See the use restrictions.
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.IO;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.ADF.CATIDs;
using ESRI.ArcGIS.Controls;
using System.Windows.Forms;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.DataSourcesRaster;
using ESRI.ArcGIS.DataSourcesGDB;
using ESRI.ArcGIS.esriSystem;
namespace SubsetRasterLayer
{
/// <summary>
/// Class used to pass the task information to the working thread
/// </summary>
public class TaskInfo
{
//defauld constructor
public TaskInfo()
{
}
public TaskInfo(int BandID, ManualResetEvent doneEvent)
{
m_bandID = BandID;
m_doneEvent = doneEvent;
}
//public RegisteredWaitHandle Handle = null;
private int m_bandID = 0;
//the even used to signal the main thread that the thread has finished its task
private ManualResetEvent m_doneEvent;
private int m_rows;
private int m_columns;
private int m_i0;
private int m_j0;
private string m_outputRasterName;
private string m_outputRasterPath;
private string m_inputRasterName;
private string m_inputRasterWSConnectionProps;
private esriWorkspaceType m_workspaceType;
public int BandID
{
get { return m_bandID; }
set { m_bandID = value; }
}
public ManualResetEvent DoneEvent
{
get { return m_doneEvent; }
set { m_doneEvent = value; }
}
public string InputRasterName
{
get { return m_inputRasterName; }
set { m_inputRasterName = value; }
}
public string OutputRasterPath
{
get { return m_outputRasterPath; }
set { m_outputRasterPath = value; }
}
public string OutputRasterName
{
get { return m_outputRasterName; }
set { m_outputRasterName = value; }
}
public int Columns
{
get { return m_columns; }
set { m_columns = value; }
}
public int Rows
{
get { return m_rows; }
set { m_rows = value; }
}
public esriWorkspaceType WorkspaceType
{
get { return m_workspaceType; }
set { m_workspaceType = value; }
}
public string InputRasterWSConnectionProps
{
get { return m_inputRasterWSConnectionProps; }
set { m_inputRasterWSConnectionProps = value; }
}
public int I0
{
get { return m_i0; }
set { m_i0 = value; }
}
public int J0
{
get { return m_j0; }
set { m_j0 = value; }
}
}
/// <summary>
/// Summary description for SubsetRasterTool.
/// </summary>
[Guid("9587dd55-595f-4feb-99f9-56f1bdb4d09c")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("SubsetRasterLayer.SubsetRasterTool")]
public sealed class SubsetRasterTool : BaseTool
{
#region COM Registration Function(s)
[ComRegisterFunction()]
[ComVisible(false)]
static void RegisterFunction(Type registerType)
{
// Required for ArcGIS Component Category Registrar support
ArcGISCategoryRegistration(registerType);
//
// TODO: Add any COM registration code here
//
}
[ComUnregisterFunction()]
[ComVisible(false)]
static void UnregisterFunction(Type registerType)
{
// Required for ArcGIS Component Category Registrar support
ArcGISCategoryUnregistration(registerType);
//
// TODO: Add any COM unregistration code here
//
}
#region ArcGIS Component Category Registrar generated code
/// <summary>
/// Required method for ArcGIS Component Category registration -
/// Do not modify the contents of this method with the code editor.
/// </summary>
private static void ArcGISCategoryRegistration(Type registerType)
{
string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
ControlsCommands.Register(regKey);
MxCommands.Register(regKey);
}
/// <summary>
/// Required method for ArcGIS Component Category unregistration -
/// Do not modify the contents of this method with the code editor.
/// </summary>
private static void ArcGISCategoryUnregistration(Type registerType)
{
string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
ControlsCommands.Unregister(regKey);
MxCommands.Unregister(regKey);
}
#endregion
#endregion
#region class members
private enum FormatType
{
IMG = 0,
TIFF,
BMP,
GRID
}
private IHookHelper m_pHookHelper = null;
private IRasterLayer m_rasterLayer = null;
private frmProgress m_progressDlg = null;
private string m_outputRasterPath = string.Empty;
private string m_outputRasterName = string.Empty;
private string m_strInputRasterWorkspaceConnectionProps = string.Empty;
private string m_inputRasterName = string.Empty;
private int m_intRows = 0;
private int m_intCols = 0;
private int m_intBandCount = 0;
private int m_i0 = 0;
private int m_j0 = 0;
private esriWorkspaceType m_workSpaceType;
private FormatType m_outputFormat;
//used in order to block access to the shared resource (raster dataset)
private static AutoResetEvent m_autoEvent = new AutoResetEvent(false);
private object m_lockObject = null;
#endregion
#region class constructor
public SubsetRasterTool()
{
base.m_category = ".NET Samples";
base.m_caption = "Subset Raster MT";
base.m_message = "Subset Raster using multi threading";
base.m_toolTip = "Subset Raster MT";
base.m_name = base.m_category + "_" + base.m_caption;
try
{
string bitmapResourceName = GetType().Name + ".bmp";
base.m_bitmap = new Bitmap(GetType(), bitmapResourceName);
base.m_cursor = new System.Windows.Forms.Cursor(GetType(), GetType().Name + ".cur");
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message, "Invalid Bitmap");
}
}
#endregion
#region Overriden Class Methods
/// <summary>
/// Occurs when this command is created
/// </summary>
/// <param name="hook">Instance of the application</param>
public override void OnCreate(object hook)
{
if (m_pHookHelper == null)
m_pHookHelper = new HookHelperClass();
m_pHookHelper.Hook = hook;
}
public override void OnMouseDown(int Button, int Shift, int X, int Y)
{
try
{
//use rubber-band in order to getthe subset area from the user
IRubberBand2 rubberBand = new RubberEnvelopeClass();
IEnvelope envelope = (IEnvelope)rubberBand.TrackNew(m_pHookHelper.ActiveView.ScreenDisplay, null);
if (null == envelope || envelope.IsEmpty)
return;
// verify that there are layers in the map
if (m_pHookHelper.FocusMap.LayerCount == 0)
return;
// get the top raster layer
m_rasterLayer = null;
IEnumLayer layers = m_pHookHelper.FocusMap.get_Layers(null, false);
layers.Reset();
ILayer layer = layers.Next();
while (layer != null)
{
if (layer is IRasterLayer)
{
m_rasterLayer = (IRasterLayer)layer;
break;
}
layer = layers.Next();
}
if (m_rasterLayer == null)
{
MessageBox.Show("There is no raster layer in the map");
return;
}
// assert if there is no overlap between the user envelope and the top raster
envelope.Intersect(m_rasterLayer.AreaOfInterest);
if (envelope.IsEmpty)
{
MessageBox.Show("The delineated envelope does not intersect the topmost raster in the map");
return;
}
//query number of bands in the input raster
m_intBandCount = m_rasterLayer.BandCount;
IDataset dataset = (IDataset)m_rasterLayer;
//get the workspace connection props since we'll need to connect it from each of the worker threads
IPropertySet connectionProps = dataset.Workspace.ConnectionProperties;
//get the workspace type
m_workSpaceType = dataset.Workspace.Type;
//serialize the connection properties into a string in order to avoid cross apartment calls
IXMLSerializer xmlSerializer = new XMLSerializerClass();
m_strInputRasterWorkspaceConnectionProps = xmlSerializer.SaveToString(connectionProps, null, null);
//m_inputRasterName = dataset.Name;
m_inputRasterName = m_rasterLayer.FilePath;
//open a folder-browser in order to get the output location of the subset raster
FolderBrowserDialog folderDlg = new FolderBrowserDialog();
folderDlg.Description = "Select output folder for the subset raster";
folderDlg.ShowNewFolderButton = true;
if (DialogResult.OK != folderDlg.ShowDialog())
return;
//get the output folder that the user had chosen
m_outputRasterPath = folderDlg.SelectedPath;
//get the input raster layer name (will be used in order to create the output raster)
string inputRasterLayerName = m_rasterLayer.Name;
if (inputRasterLayerName.IndexOf(".") != -1)
inputRasterLayerName = inputRasterLayerName.Substring(0, inputRasterLayerName.IndexOf("."));
//set the output format
m_outputFormat = FormatType.IMG;
//create the output raster dataset
CreateRasterDataset(m_rasterLayer, m_outputRasterPath, inputRasterLayerName + "_Subset.img", envelope);
//show the output progress form
m_progressDlg = new frmProgress();
//force the control creation in order to make sure that the form has a handle
m_progressDlg.CreateControl();
//set the statusbar message of the progress dialog
m_progressDlg.SetStatusBarMessage("Subsetting raster " + m_rasterLayer.Name);
//show the progress dialog
m_progressDlg.Show();
//run the subset thread. The subset process uses WaitHandle in order to
//wait on the subsetting threads to finish their task. In order to use WaitHandle.WaitAll()
//the call must be made from a MTA. The current command is STA and therefore a thread must be created
//in order to allow this(.NET threads gets created as MTA by default).
Thread t = new Thread(new ThreadStart(SubsetProc));
t.Start();
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
public override void OnMouseMove(int Button, int Shift, int X, int Y)
{
// TODO: Add SubsetRasterTool.OnMouseMove implementation
}
public override void OnMouseUp(int Button, int Shift, int X, int Y)
{
// TODO: Add SubsetRasterTool.OnMouseUp implementation
}
#endregion
/// <summary>
/// main subset method
/// </summary>
/// <remarks>Please note that this sample does not copy colormap
/// in case where it is present. The outcome will be black and white</remarks>
private void SubsetProc()
{
try
{
m_lockObject = new object();
//set the message on the progress dialog statusbar
m_progressDlg.SetStatusBarMessage("starting subset process");
//create ManualResetEvent in order to notify the main threads that all threads
//are done with their task
ManualResetEvent[] doneEvents = new ManualResetEvent[m_intBandCount];
//create the subset threads
Thread[] threadTask = new Thread[m_intBandCount];
//create a thread for each of the bands of the input raster
//each task will subset a different raster-band. The information required for the
//subsetting the raster-band will be passed to the task by the user-defined
//class TaskInfo
for (int i = 0; i < m_intBandCount; i++)
{
//create the ManualResetEvent flad for the task in order to signal the waiting thread
//that the task had been completed
doneEvents[i] = new ManualResetEvent(false);
//create the Task-Information for the thread and pass in the relevant information
//Pleas note that all the information is passed as simple types
TaskInfo ti = new TaskInfo(i, doneEvents[i]);
ti.WorkspaceType = m_workSpaceType;
ti.Rows = m_intRows;
ti.Columns = m_intCols;
ti.I0 = m_i0;
ti.J0 = m_j0;
ti.InputRasterName = m_inputRasterName;
ti.OutputRasterPath = m_outputRasterPath;
ti.OutputRasterName = m_outputRasterName;
ti.InputRasterWSConnectionProps = m_strInputRasterWorkspaceConnectionProps;
// assign the subsetting thread for the rasterband.
threadTask[i] = new Thread(new ParameterizedThreadStart(SubsetRasterBand));
// Note the STA apartment which is required to run ArcObjects
threadTask[i].SetApartmentState(ApartmentState.STA);
threadTask[i].Name = "Subset_" + (i + 1).ToString();
// start the task and pass the task information
threadTask[i].Start((object)ti);
}
//Sets the state of the event to signaled, thus allowing one or more of the waiting threads to proceed
m_autoEvent.Set();
// Wait for all threads to complete their task...
WaitHandle.WaitAll(doneEvents);
// verify that all tasks are done and that the memory is cleaned
for (int i = 0; i < m_intBandCount; i++)
{
threadTask[i].Join();
}
threadTask = null;
// spin a new thread in order to claculate statistics and pyramid layers
Thread additionalDataTask = new Thread(new ParameterizedThreadStart(CalculateStatsAndPyramids));
additionalDataTask.SetApartmentState(ApartmentState.STA);
additionalDataTask.Name = "calc_aux_data";
additionalDataTask.Start(System.IO.Path.Combine(m_outputRasterPath, m_outputRasterName));
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
/// <summary>
/// Opens the new raster dataset, calculate statistics and build pyramids
/// </summary>
/// <param name="data"></param>
private void CalculateStatsAndPyramids(object data)
{
string path = System.IO.Path.GetDirectoryName((string)data);
string fileName = System.IO.Path.GetFileName((string)data);
IRasterDataset rasterDataset = OpenOutputRasterDataset(path, fileName);
IRasterBandCollection rasterBandColl = (IRasterBandCollection)rasterDataset;
// calculate statistics and histogram
for (int i = 0; i < rasterBandColl.Count; i++)
{
m_progressDlg.SetStatusBarMessage("Computing statistics for band #" + (i + 1));
IRasterBand newRasterBand = rasterBandColl.Item(i);
newRasterBand.ComputeStatsAndHist();
}
// build pyramids
m_progressDlg.SetStatusBarMessage("Building pyramids");
((IRasterPyramid)rasterDataset).Create();
//set the message on the progress dialog
m_progressDlg.SetStatusBarMessage("Finished subsetting raster " + m_inputRasterName);
//close the progress dialog
m_progressDlg.CloseDlg();
}
/// <summary>
/// create a new raster dataset based on input rasterLayer and a given extent
/// </summary>
/// <param name="rasterLayer">The input raster layer</param>
/// <param name="path">path for the output subser raster</param>
/// <param name="rasterName">name of the output subset raster</param>
/// <param name="extent">extent of the subset raster</param>
private void CreateRasterDataset(IRasterLayer rasterLayer, string path, string rasterName,IEnvelope extent)
{
try
{
//cache the name of the output subset raster
m_outputRasterName = rasterName;
//create a new workspace-factory
IWorkspaceFactory workspaceFact = new RasterWorkspaceFactoryClass();
//open a raster workspace
IRasterWorkspace2 rasterWorkspace = (IRasterWorkspace2)workspaceFact.OpenFromFile(path, 0);
//in case that there is already an existing raster with the raster name, try to delete it
if (System.IO.File.Exists(System.IO.Path.Combine(path, rasterName)))
{
IDataset dataset = rasterWorkspace.OpenRasterDataset(rasterName) as IDataset;
dataset.Delete();
}
//get the raster layer properties
IRasterProps rasterProps = (IRasterProps)rasterLayer.Raster;
//the input raster cellsize (please note that it is possible that it will not be square)
double cellSizeX = rasterProps.MeanCellSize().X;
double cellSizeY = rasterProps.MeanCellSize().Y;
//the stating indices (in pixels) for the upper left corner of the subset raster
m_i0 = Convert.ToInt32((extent.XMin - rasterProps.Extent.XMin) / cellSizeX);
m_j0 = Convert.ToInt32(rasterProps.Height - ((extent.YMax - rasterProps.Extent.YMin) / cellSizeY));
//calculate the number of rows and columns for subset
m_intCols = Convert.ToInt32(extent.Width / cellSizeX + 0.5);
m_intRows = Convert.ToInt32(extent.Height / cellSizeY + 0.5);
//need to shift the point of origin so that it will allign with the origonal raster pixels
double llx = rasterProps.Extent.XMin + m_i0 * cellSizeX;
double lly = rasterProps.Extent.YMin + (rasterProps.Height - (m_intRows + m_j0)) * cellSizeY;
IPoint point = new PointClass();
point.PutCoords(llx, lly);
string formatType = string.Empty;
switch (m_outputFormat)
{
case FormatType.IMG:
formatType = "IMAGINE Image";
break;
case FormatType.TIFF:
formatType = "TIFF";
break;
case FormatType.GRID:
formatType = "GRID";
break;
case FormatType.BMP:
formatType = "BMP";
break;
}
//create the subset raster dataset
rasterWorkspace.CreateRasterDataset(rasterName,
formatType,
point,
m_intCols,
m_intRows,
cellSizeX,
cellSizeY,
rasterLayer.BandCount,
rasterProps.PixelType,
rasterProps.SpatialReference,
true);
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
/// <summary>
/// subset task method
/// </summary>
/// <param name="state"></param>
private void SubsetRasterBand(object state)
{
try
{
System.Diagnostics.Trace.WriteLine(Thread.CurrentThread.GetApartmentState());
// The state object must be cast to the correct type, because the
// signature of the WaitOrTimerCallback delegate specifies type
// Object.
TaskInfo ti = (TaskInfo)state;
//set the message on the progress dialog statusbar
m_progressDlg.SetStatusBarMessage("Copy RasterBand #" + (ti.BandID + 1).ToString());
IRasterDataset rasterataset = OpenRasterDataset(ti);
//cast the input raster bandCollection
IRasterBandCollection rasterBaneCollection = (IRasterBandCollection)rasterataset;
//get the raster band from which to copy the data
IRasterBand rasterBand = rasterBaneCollection.Item(ti.BandID);
IRawPixels rawPixels = (IRawPixels)rasterBand;
//open the newly created raster dataset
IRasterDataset newRasterDataset = OpenOutputRasterDataset(ti.OutputRasterPath, ti.OutputRasterName);
//get the raster-band into which data will be copied
IRasterBandCollection newRasterBandCollection = (IRasterBandCollection)newRasterDataset;
IRasterBand newRasterBand = newRasterBandCollection.Item(ti.BandID);
IRawPixels newRasterPixels = (IRawPixels)newRasterBand;
//set the size of the pixelBlock
int pixBlockSizeX = Math.Min(128, m_intCols);
int pixBlockSizeY = Math.Min(128, m_intRows);
//calculate the progressbar MaxValue
int hBlocks = Convert.ToInt32(m_intCols / pixBlockSizeX + 0.5);
int vBlocks = Convert.ToInt32(m_intRows / pixBlockSizeY + 0.5);
//set the maximum value of the progress controls of the progress dialog
if (ti.BandID == 0)
m_progressDlg.SetProgressbarMaximum(hBlocks * vBlocks);
//set the upper left pixel from which the subset starts
IPnt pnt = new PntClass();
pnt.SetCoords(Convert.ToDouble(pixBlockSizeX), Convert.ToDouble(pixBlockSizeY));
//instantiate the Top-Left-Corner to be used with the pixel-block for the copying process
IPnt tlc = new PntClass();
Monitor.Enter(m_lockObject);
//create the pixelBlock for the copying process
IPixelBlock pixelBlock = rawPixels.CreatePixelBlock(pnt);
Monitor.Exit(m_lockObject);
int p = 0;
int q = 0;
//read the raster band info from the input raster-band and write it to the new raster-band
for (int j = ti.J0; j < (ti.J0 + m_intRows); j += pixBlockSizeY)
{
for (int i = ti.I0; i < (ti.I0 + m_intCols); i += pixBlockSizeX)
{
//set the upper-left corner coordinate
tlc.SetCoords(Convert.ToDouble(i), Convert.ToDouble(j));
// block the other thread while reading from the raster
m_autoEvent.WaitOne();
//read the pixels from the input raster-band into the pixel-block
rawPixels.Read(tlc, (PixelBlock)pixelBlock);
m_autoEvent.Set();
//set the upper-left corner coordinate in order to write into the subset raster
tlc.SetCoords(Convert.ToDouble(p), Convert.ToDouble(q));
// block the other thread while writing to the output raster
m_autoEvent.WaitOne();
//write the pixels to the output rasterband
newRasterPixels.Write(tlc, (PixelBlock)pixelBlock);
m_autoEvent.Set();
//increment the horizontal coordinate of the output rasterband
p += pixBlockSizeX;
//increment the progressbars
if (ti.BandID < 3)
{
m_progressDlg.IncrementProgressBar(ti.BandID + 1);
}
}
//reset the horizontal coordinate of the output rasterband
p = 0;
//increment the vertical coordinate of the output rasterband
q += pixBlockSizeY;
}
//// wait untill all copy thraeds are done copying
////calculate statistics
////need to lock all other threads in order to write to the rasterband
//m_autoEvent.WaitOne();
////Monitor.Enter(m_lockObject);
//m_progressDlg.SetStatusBarMessage("compute statistics for band #" + (ti.BandID + 1).ToString());
//newRasterBand.ComputeStatsAndHist();
////set the message on the progress dialog statusbar
//m_progressDlg.SetStatusBarMessage("done subsetting band #" + (ti.BandID + 1).ToString());
//////signal the next available thread to write to the subset dataset
////Monitor.Exit(m_lockObject);
//m_autoEvent.Set();
//signal the main thread the that thread has finished its task
ti.DoneEvent.Set();
}
catch (Exception ex)
{
//m_autoEvent.Set();
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
/// <summary>
/// Opens the new raster dataset
/// </summary>
/// <param name="rasterPath">path of new raster dataset</param>
/// <param name="rasterName">name of new raster dataset</param>
/// <returns></returns>
private IRasterDataset OpenOutputRasterDataset(string rasterPath, string rasterName)
{
IWorkspaceFactory2 newRasterWorkspaceFactory = new RasterWorkspaceFactoryClass();
Monitor.Enter(m_lockObject);
IRasterWorkspace2 newRasterWorkspace = newRasterWorkspaceFactory.OpenFromFile(rasterPath, 0) as IRasterWorkspace2;
IRasterDataset newRasterDataset = newRasterWorkspace.OpenRasterDataset(rasterName);
Monitor.Exit(m_lockObject);
return newRasterDataset;
}
/// <summary>
/// given the task information, opens the raster dataset
/// </summary>
/// <param name="ti"></param>
/// <returns></returns>
private IRasterDataset OpenRasterDataset(TaskInfo ti)
{
if (null == ti)
return null;
//deserialize the workspace connection properties
IXMLSerializer xmlSerializer = new XMLSerializerClass();
object obj = xmlSerializer.LoadFromString(ti.InputRasterWSConnectionProps, null, null);
if (!(obj is IPropertySet))
{
System.Diagnostics.Trace.WriteLine("object is not a PropertySet...");
return null;
}
IPropertySet workspaceProperties = (IPropertySet)obj;
//open the input raster workspace factory
IWorkspaceFactory2 workspaceFactory = CreateWorkspaceFactory(ti);
//open the workspace of the input raster
Monitor.Enter(m_lockObject);
IRasterWorkspace2 rasterWorkSpace = workspaceFactory.Open(workspaceProperties, 0) as IRasterWorkspace2;
if (null == rasterWorkSpace)
{
System.Diagnostics.Trace.WriteLine("error opening raster workspace...");
return null;
}
//open the input raster dataset
IRasterDataset rasterataset = rasterWorkSpace.OpenRasterDataset(System.IO.Path.GetFileName(ti.InputRasterName));
Monitor.Exit(m_lockObject);
return rasterataset;
}
/// <summary>
/// create an instance of the right workspace factory
/// </summary>
/// <param name="ti"></param>
/// <returns></returns>
IWorkspaceFactory2 CreateWorkspaceFactory(TaskInfo ti)
{
//test the type of input raster workspace
switch (ti.WorkspaceType)
{
case esriWorkspaceType.esriFileSystemWorkspace:
return new RasterWorkspaceFactoryClass();
//in the case of a local-workspace it can be FileBased GDB or an access DB
case esriWorkspaceType.esriLocalDatabaseWorkspace:
if (ti.InputRasterName.IndexOf(".mdb") != -1)
return new AccessWorkspaceFactoryClass();
else
return new FileGDBWorkspaceFactoryClass();
case esriWorkspaceType.esriRemoteDatabaseWorkspace:
return new SdeWorkspaceFactoryClass();
}
return null;
}
}
}