RasterIntervals.sml

  Download

More scripts: Advanced

Syntax Highlighing:

comments, key words, predefined symbols, class members & methods, functions & classes
            
##  RasterIntervals.sml
##  Sample script for Introduction to Geospatial Scripting.
##  Standalone script to categorize a grayscale raster into a
##	 specified number of grayscale-value intervals.  The number of
##  intervals and the distribution type (equal count or equal interval)
##  can be specified.  Separate dialogs are provided to set input
##  parameters, view and edit the range values, and to preview the
##	 result.  Script makes a categorical raster with a single value
##	 for each interval and a color palette.  
class RASTER RastIn, RastClass;		# class instances for input and output rasters
class RASTER Temp;						# class instance for temporary raster for result preview
class COLORMAP palette;					# class instance for color palette for output raster
numeric maxRast;		# input raster maximum value
numeric minRast;		# input raster minimum value
numeric globalRange;		# global range of raster values
numeric numInterval;		# number of intervals to create
numeric distribType;	# flag for type of interval: 0 = equal range, 1 = equal count
numeric i, j;				# counters for processing loops
numeric value;			# current raster value in input
numeric globalNumCells;		# number of non-null cells in raster
numeric counter;	# counter for running count of cells
array numeric minRange[1];		# array to hold minimum boundary values for ranges
array numeric maxRange[1];		# array to hold maximum boundary values for ranges
array numeric cellCount[1];	# array to hold final cell count for each interval
array numeric cellPct[1];		# array to hold cell percentage for each interval
#### variables for color palette structure
string xmlPalette$;
class XMLDOC docColor;
### variables for dialog windows
string xmlmain$;					# string variable for main dialog specification
class XMLDOC docmain;			# class instance for parsed XML text containing main dialog specification
class XMLNODE nodemain;			# class instance for dialog node in XML structure for main dialog window 
class GUI_DLG dlgmain;			# dialog class instance for main dialog window
numeric errXML, dlgreturn;		# variables for error checking with main XML and dialog
string xmldistrib$;				# string variable for Distribution dialog specification
class XMLDOC docdistrib;		# class instance for parsed XML text containing Distribution dialog specification
class XMLNODE nodedistrib;		# class instance for dialog node in XML structure for Distribution dialog window
class GUI_DLG dlgDistrib;		# dialog class instance for Distribution dialog window
string xmlPreview$;				# string variable for Preview dialog specification
class XMLDOC docPreview;		# class instance for parsed XML text containing Preview dialog specification
class XMLNODE nodePreview;		# class instance for dialog node in XML structure for Preview dialog window
class GUI_DLG dlgPreview;		# dialog class instance for Preview window 
class GRE_VIEW view;					# class instance for the view in the Preview window
#####################################################################
########  Procedure and Function Definitions  ####################### 
#####################################################################
#######################################################################
### Define function to return the class interval of the current raster 
### value by comparison to range boundaries
func SetInterval(valueIn) {
	for i = 1 to numInterval {
		if (valueIn >= minRange[i] && valueIn <= maxRange[i]) then
		return i;
		}
	}
########################################################################
### Define procedure to update cell counts and percentages for intervals
proc updateCounts (numeric indexStart, numeric indexEnd)
	{
	local numeric i, counter;
	## reset cell counts for relevant intervals to 0
	for i = indexStart to indexEnd
		{
		cellCount[i] = 0;
		}
	## loop through input raster to get cell count for each specified interval 
	for each RastIn 
		{
		++ counter;
		for i = indexStart to indexEnd
			{
			if (RastIn >= minRange[i] && RastIn <= maxRange[i]) then
				++ cellCount[i];
			}
		}
	# compute cell percentage for each interval
	for i = indexStart to indexEnd 
		{
		cellPct[i] = 100 * cellCount[i] / counter; 
		}
	}
#####################################################
### Callback procedure to initally disable OK button
### on main dialog window when it opens
proc onOpenMain () 
	{
	dlgmain.SetOkEnabled( 0 );
	}
#######################################################
### Callback procedure to select input grayscale raster
proc getRastIn ()
	{
	GetInputRaster( RastIn );
	if ( RastIn.$Info.Type == "16-bit color RGB" or RastIn.$Info.Type == "16-bit color BGR"
		or RastIn.$Info.Type == "24-bit color RGB" or RastIn.$Info.Type == "24-bit color BGR"
		or RastIn.$Info.Type == "binary" or RastIn.$Info.Type == "32-bit float" or RastIn.$Info.Type == "64-bit float" )
		{
		PopupMessage( sprintf("Selected raster type is: %s. \n Please select a valid grayscale integer raster.", RastIn.$Info.Type) );
		}
	else
		{
		local string filename$, objname$, dlgtext$; 
		local numeric objnum;
		local class GUI_CTRL_PUSHBUTTON runButton;
		filename$ = GetObjectFileName( RastIn );
		objnum = GetObjectNumber( RastIn );
		objname$ = GetObjectName( filename$, objnum );
		dlgtext$ = sprintf( "%s.%s /  %s", FileNameGetName(filename$), FileNameGetExt(filename$), objname$ );
		dlgmain.SetCtrlValueStr("inRastText", dlgtext$);
		maxRast = GlobalMax(RastIn);
		minRast = GlobalMin(RastIn);
		globalRange = maxRast - minRast + 1;
		runButton = dlgmain.GetCtrlByID("runButton");
		runButton.SetEnabled(1);
		}
	}
########################################################
### Callback procedure to select output interval raster
proc getRastOut ()
	{
	local string filename$, objname$, dlgtext$; 
	local numeric objnum;
	GetOutputRaster(RastClass, RastIn.$Info.NumLins, RastIn.$Info.NumCols, "8-bit unsigned");
	filename$ = GetObjectFileName( RastClass );
	objnum = GetObjectNumber( RastClass );
	objname$ = GetObjectName( filename$, objnum );
	dlgtext$ = sprintf( "%s.%s /  %s", FileNameGetName(filename$), FileNameGetExt(filename$), objname$ );
	}
#########################################################
### Procedure to make interval raster with color palette.
### Used for temporary preview raster and for output raster
proc makeIntervalRaster(class Raster Rast)
	{
	local class GUI_CTRL_COLORBUTTON colorbutton;
	local class ColorMap palette;
	local class COLOR color; 
	for each RastIn
		{
		value = RastIn;
		Rast = SetInterval(value);
		}
	CreateHistogram(Rast, 0);
	CreatePyramid(Rast);
	CopySubobjects(RastIn, Rast, "GEOREF");
	###############################################################
	### create color palette for output raster from the colors in the
	### distribution dialog
	for i = 1 to numInterval
		{
		local string interval$ = NumToStr(i);
		colorbutton = dlgDistrib.GetCtrlByID(sprintf("colorbut%s", interval$));
		color = colorbutton.GetColor();
		ColorMapSetColor(palette, i, color); 
		}
	ColorMapWriteToRastVar(Rast, palette, "IntervalColors", "Color palette");
	SetNull(Rast, 0);
	}
###############################################################
### Callback procedure when Distribution dialog opens.  Sets
### ioitial colors of colorbuttons before opening (there is no
### way to do this using the XML structure, so must access the GUI
### control class for the colorbutton directly.  Since this dialog is
### modal, it isn't created until DoModal method is called, so buttons
### must be set using the dialog's OnOpen callback.
proc onOpenDistrib ()
	{
	### loop through the intervals to assign colors
	for i = 1 to numInterval
		{
		local string interval$ = NumToStr(i);
		# generate color as HIS and convert to RGB and store in COLOR class instance
		class COLOR color;
		class XMLNODE colorNode;
		colorNode = docColor.GetElementByID(interval$);
		color.red = colorNode.GetAttributeNum("red");
		color.green = colorNode.GetAttributeNum("green");
		color.blue = colorNode.GetAttributeNum("blue");
		# get handle for relevant colorbutton control in dialog
		class GUI_CTRL_COLORBUTTON colorbutton;
		colorbutton = dlgDistrib.GetCtrlByID(sprintf("colorbut%s", interval$));
		colorbutton.SetColor(color);
		}
	}
########################################################################
### Callback procedure for OK button on Distribution dialog.
### Writes values to the output raster and sets up the color palette.
proc onOKdistrib ()
	{
	###################################
	### fill in values in output raster
	getRastOut();
	makeIntervalRaster(RastClass);
	dlgDistrib.Close(0);
	} # end onOKdistrib()
#########################################################################
### Callback procedure for Cancel button on Distribution dialog.
proc onCancelDistrib ()
	{
	dlgDistrib.Close(0);
	}
################################################################
### Procedure called when Exit button on main dialog is pressed.
### Closes dialog and exits script.
proc onExit ()
	{
	dlgmain.Close(0);
	Exit();
	}
#################################################################
### Procedure called when Close button on Preview window is pressed.
proc onPreviewClose ()
	{
	dlgPreview.Close(0);
	}
##################################################################
### Procedure called when the Preview dialog window opens
proc onPreviewOpen ()
	{
	view.Redraw();
	}
#####################################################################
### Procedure called when Preview dialog is initialized.  Used to create
### view within the dialog and to create a group to view the temporary raster.
proc onInitDialog () 
	{
	local class GUI_LAYOUT_PANE viewpane; 	# class instance for layout pane to contain the view
	local class widget viewpaneWidget;		# class for widget for layout pane to serve as parent for the view
	local class GRE_GROUP viewgp;				# class for group to be shown in the view
	local class GRE_LAYER_RASTER tempLayer;
	viewpane = dlgPreview.GetPaneByID("viewpane");		# get handle for layout pane from dialog
	viewpaneWidget = viewpane.GetWidget();					# get parent Xm widget for pane
	viewpaneWidget.Resizable = 1;								# set parent widget to be resizable
	viewgp = GroupCreate();										# create display group
	tempLayer = GroupQuickAddRasterVar(viewgp, Temp);	# add the Temp raster to the group
	tempLayer.DataTip.Prefix = "Interval: ";
	view = viewgp.CreateView(viewpaneWidget,"",450, 350, "NoCloseOption");		# create 2D view of group in dialog
	#view.ScalePosVisible = 0;		# hide scale/position report
#	view.LegendView.Show = "Left";
	}
######################################################################
### Procedure called when Preview Result button on Distribution dialog
### is pressed.
proc onPreview ()
	{
	CreateTempRaster(Temp, RastIn.$Info.NumLins, RastIn.$Info.NumCols, "8-bit unsigned");
	makeIntervalRaster(Temp);
	# dialog specification for Preview dialog window
	xmlPreview$='<?xml version="1.0"?>
	<root>
		<dialog id="previewDlg" Title="Preview" Buttons="" HorizResize="Expand" VertResize="Expand" OnOpen="onPreviewOpen()">
			<pane id="viewpane" Orientation = "vertical" HorizResize="Expand" VertResize="Expand"/>
			<pane Orientation="horizontal" HorizAlign="right" HorizResize="Fixed" VertResize="Fixed">
				<pushbutton Name="  Close  " VertResize="Fixed" VertAlign="Bottom" OnPressed="onPreviewClose()"/> 
			</pane>
		</dialog>
	</root>';
	# parse XML text for the dialog into memory; 
	# return an error code (number < 0 ) if there are syntax errors
	errXML = docPreview.Parse(xmlPreview$);
	if (errXML < 0) 
		{
		PopupError(errXML); 	# Popup an error dialog. "Details" button shows syntax errors.
		Exit();
		}
	# get the dialog element from the parsed XML document and
	# show error message if the dialog element can't be found
	nodePreview = docPreview.GetElementByID("previewDlg");
	if (nodePreview == 0) 
		{
		PopupMessage("Could not find dialog node in XML document");
		Exit();
		}
	# Set the XML dialog element as the source for the GUI_DLG class instance
	# we are using for the dialog window.
	dlgPreview.SetXMLNode(nodePreview);
	dlgPreview.SetOnInitDialog(onInitDialog);
	dlgPreview.DoModal();
	}
###########################################################
### Callback for edit of any interval Minimum value editnumber field
proc onChangeMin(class GUI_CTRL_EDIT_NUMBER this, numeric i)
	{
	local numeric j = i - 1;
	local numeric val = this.GetValueNum();
	local numeric prevMin, thisMax, prevRng, thisRng;
	local class GUI_CTRL_LABEL prevRngLabel, thisRngLabel;
	local class GUI_CTRL_LABEL prevCountLabel, thisCountLabel;
	local class GUI_CTRL_LABEL prevPctLabel, thisPctLabel; 
	### check that new minimum is greater than previous interval minimum
	### and less than its own interval maximum and reset if necessary
	prevMin = dlgDistrib.GetCtrlValueNum(sprintf("editMin%d", j));
	thisMax = dlgDistrib.GetCtrlValueNum(sprintf("editMax%d", i));
	if ( val <= prevMin )
		{
		val = prevMin + 2;
		this.SetValue( val, 0 );
		}
	else
		{ 
		if ( val > thisMax )
			{
			val = thisMax - 1;
			this.SetValue( val, 0 );
			}
		} 
	# reset minimum value in minRange array 
	minRange[i] = val;
	# reset maximum value of next lower interval in dialog and in maxRange array
	maxRange[j] = minRange[i] - 1;
	dlgDistrib.SetCtrlValueNum( sprintf("editMax%d", j), maxRange[j] );
	# compute new ranges for j and i intervals and update labels on dialog
	prevRng = val - prevMin;
	thisRng = thisMax - val + 1;
	prevRngLabel = dlgDistrib.GetCtrlByID( sprintf("labelRng%d", j) );
	prevRngLabel.SetLabel( NumToStr(prevRng) );
	thisRngLabel = dlgDistrib.GetCtrlByID( sprintf("labelRng%d", i) );
	thisRngLabel.SetLabel( NumToStr(thisRng) );
	# update counts and percentages
	updateCounts(j, i);
	prevCountLabel = dlgDistrib.GetCtrlByID( sprintf("labelCount%d", j) );
	prevCountLabel.SetLabel( NumToStr(cellCount[j] ) );
	thisCountLabel = dlgDistrib.GetCtrlByID( sprintf("labelCount%d", i) );
	thisCountLabel.SetLabel( NumToStr(cellCount[i] ) );
	prevPctLabel = dlgDistrib.GetCtrlByID( sprintf("labelPct%d", j) );
	prevPctLabel.SetLabel( sprintf("%.2f", cellPct[j] ) );
	thisPctLabel = dlgDistrib.GetCtrlByID( sprintf("labelPct%d", i) );
	thisPctLabel.SetLabel( sprintf("%.2f", cellPct[i] ) );
	}	# end onChangeMin()
###########################################################
### Callback for edit of any interval Maximum value editnumber field
proc onChangeMax(class GUI_CTRL_EDIT_NUMBER this, numeric i)
	{
	local numeric j = i + 1;
	local numeric val = this.GetValueNum();		# new value for editnumber field
	local numeric thisMin, nextMax, thisRng, nextRng;
	local class GUI_CTRL_LABEL thisRngLabel, nextRngLabel;
	local class GUI_CTRL_LABEL thisCountLabel, nextCountLabel;
	local class GUI_CTRL_LABEL thisPctLabel, nextPctLabel; 
	### check that new maximum is greater than its own interval minimum
	### and less than the next interval maximum and reset if necessary
	thisMin = dlgDistrib.GetCtrlValueNum( sprintf( "editMin%d", i ) );
	nextMax = dlgDistrib.GetCtrlValueNum( sprintf( "editMax%d", j ) );
	if ( val < thisMin )
		{
		val = thisMin + 1;
		this.SetValue( val, 0 );
		}
	else
		{
		if ( val >= nextMax )
			{
			val = nextMax - 2;
			this.SetValue( val, 0 );
			}
		}
	# read new maximum value for interval from editnumber and reset value in maxRange array
	maxRange[i] = val;
	# reset minimum value of next higher interval in dialog and in minRange array
	minRange[j] = maxRange[i] + 1;
	dlgDistrib.SetCtrlValueNum(sprintf("editMin%d", j), minRange[j]);
	# compute new ranges for i and j intervals and update labels on dialog
	thisRng = val - thisMin + 1;
	nextRng = nextMax - val;
	thisRngLabel = dlgDistrib.GetCtrlByID( sprintf("labelRng%d", i) );
	thisRngLabel.SetLabel( NumToStr(thisRng) );
	nextRngLabel = dlgDistrib.GetCtrlByID( sprintf("labelRng%d", j) );
	nextRngLabel.SetLabel( NumToStr(nextRng) );	 
	# update counts and percentages
	updateCounts(i, j);
	thisCountLabel = dlgDistrib.GetCtrlByID( sprintf("labelCount%d", i) );
	thisCountLabel.SetLabel( NumToStr(cellCount[i] ) );
	nextCountLabel = dlgDistrib.GetCtrlByID( sprintf("labelCount%d", j) );
	nextCountLabel.SetLabel( NumToStr(cellCount[j] ) );
	thisPctLabel = dlgDistrib.GetCtrlByID( sprintf("labelPct%d", i) );
	thisPctLabel.SetLabel( sprintf("%.2f", cellPct[i] ) );
	nextPctLabel = dlgDistrib.GetCtrlByID( sprintf("labelPct%d", j) );
	nextPctLabel.SetLabel( sprintf("%.2f", cellPct[j] ) );
	}	# end onChangeMax()
########################################################
### Callback procedure for OK button on main dialog.
### Set up intervals:
### find minimum and maximum values for each interval along with  
### cell count and cell percentage percentage and store in arrays.
### Show information in Distribution dialog.
proc setUpIntervals ()
	{
	## get desired number of intervals from the dialog
	numInterval = dlgmain.GetCtrlValueNum("numberIntervals");
	printf("Number of intervals = %d \n", numInterval);
	# resize the arrays to match the number of intervals requested
	ResizeArrayClear(minRange, numInterval);
	ResizeArrayClear(maxRange, numInterval);
	ResizeArrayClear(cellCount, numInterval);
	ResizeArrayClear(cellPct, numInterval);
	# get distribution type from dialog
	distribType = dlgmain.GetCtrlValueNum("distCombo");
	#################################
	### set up equal-range intervals
	if (distribType == 0)		 
		{
		local numeric intervalWidth;	# numeric range of interval in equal interval process
		intervalWidth = ( globalRange ) / numInterval;
		printf("interval width = %.1f\n", intervalWidth);
		minRange[1] = minRast;				# set minimum value for first interval
		maxRange[numInterval] = maxRast;	# set maximum value for last interval
		## iterate through remaining intervals and set remaining min and max values
		for i = 1 to (numInterval - 1)
			{
			## set max value for this interval; rounding ensures that no integer interval width is
			## more than 1 value away from the target interval width
			maxRange[i] = round( minRast - 1 + ( i * intervalWidth ) );
			minRange[i + 1] = maxRange[i] + 1; 	# set minimum value for next interval
			}
		## use user-defined procedure to loop through input raster to get cell count and percentage for each interval 
		updateCounts(1, numInterval);
		}
	####################################
	### set up equal-count intervals
	else if (distribType == 1)
		{
		local numeric targetCount = 0;	# number of cell counts per interval in equal-count
		local numeric targetCount1;		# current target count (offset from targetCount by remainder of previous interval)
		local numeric rem;					# difference between final interval count and current target count 
		globalNumCells = 0;
		array numeric histo[globalRange];		# array for cell value histogram
		# populate histogram array for raster and count number of non-null cells
		for each RastIn
			{
			++ histo[RastIn - minRast + 1];
			++ globalNumCells;
			}
		## compute target count per interval
		targetCount = round( globalNumCells / numInterval );
		printf("globalNumCells = %d, targetCount = %d \n", globalNumCells, targetCount);
		targetCount1 = targetCount;
		minRange[1] = minRast;
		maxRange[numInterval] = maxRast;
		j = 1;		# initialize index to histogram array
		## iterate through all but last interval and set remaining min and max values
		for i = 1 to numInterval - 1
			{
			counter = 0; 
			while ( counter <= targetCount1 && j <= globalRange )
				{
				counter += histo[j];
#				printf("i = %d, j = %d, cellCount = %d \n", i, j, counter); 
				++j;
				}
			if ( targetCount1 < (counter - ( histo[j-1] / 2 ) ) ) # target count reached before midpoint of last cell value
				{
				--j;												# decrement histogram index to last interval added
				cellCount[i] = counter - histo[j];		# omit last value from current interval
				rem = targetCount1 - cellCount[i];		# shortfall relative to current target
				targetCount1 = targetCount + rem;		# increase target count for next interval by difference
				}															# between actual and target count
			else
				{
				cellCount[i] = counter;							# keep last value in current interval
				rem = cellCount[i] - targetCount1;			# remainder relative to current target count
				targetCount1 = targetCount - rem;		# decrease target count for next interval by difference 
				}															# between actual and target count
			maxRange[i] = (j - 1) + minRast - 1;
			cellPct[i] = 100 * cellCount[i] / globalNumCells;
			minRange[i+1] = maxRange[i] + 1;			# set minimum value for next range
			}
			# count cells in last interval
			counter = 0;
			for i = j to globalRange 
				{
				counter += histo[i];
#				printf("i = %d, count = %d \n", i, counter);
				}
			cellCount[numInterval] = counter;
			cellPct[numInterval] = 100 * counter / globalNumCells;
		}
	# string variable with skeleton of XML specification of the
	# Distribution dialog window; controls and labels for the data
	# for each interval will be added to the XML structure before the
	# dialog is actually created.
	xmldistrib$ = '<?xml version="1.0"?>
	<root>
		<dialog id="distribDlg" Title="Distribution" Buttons="" OnOpen="onOpenDistrib()">
			<groupbox Name=" Global Values: " ExtraBorder="4">
				<pane Orientation="horizontal">
					<label>Minimum:</label>
					<label id="globalMin"></label>
					<label>Maximum:</label>
					<label id="globalMax"></label>
					<label>Range:</label>
					<label id="globalRange"></label>
				</pane>
			</groupbox>
			<pane Orientation="horizontal" HorizResize="Fixed">
				<label>Distribution Type: </label>
				<label id="distType"></label>
			</pane>
			<groupbox Extraborder="5">
				<pane id="distribList" Orientation="vertical">
					<pane Orientation="horizontal">
						<label WidthGroup="1" HorizAlign="Center" TextAlign="Center">Minimum</label>
						<label WidthGroup="2" HorizAlign="Center" TextAlign="Center">Maximum</label>
						<label WidthGroup="3" HorizAlign="Center" TextAlign="Center">Range</label>
						<label WidthGroup="4" HorizAlign="Center" TextAlign="Center">Count</label>
						<label WidthGroup="5" HorizAlign="Center" TextAlign="Center">Percent</label>
						<label WidthGroup="6" HorizAlign="Center" TextAlign="Center">Color</label>
					</pane>
				</pane>
			</groupbox>
			<pane Orientation="horizontal" HorizResize="fixed" HorizAlign="right">
				<pushbutton Name="  Preview result...  " OnPressed="onPreview()" HorizResize="Fixed" HorizAlign="Left"/>
				<pushbutton Name="  OK  " OnPressed="onOKdistrib()"/>
				<pushbutton Name="  Cancel  " OnPressed="onCancelDistrib()"/>
			</pane>
		</dialog>
	</root>';
	### Parse XML text for dialog into memory; return error if there are syntax errors.
	errXML = docdistrib.Parse(xmldistrib$);
	if ( errXML < 0 ) {
		PopupError( errXML );		# pop up an error dialog
		Exit( );
		}
	################################################################
	### Modify the XML structure in memory before opening the dialog
	### get XML nodes for global value labels
	class XMLNODE minLabel, maxLabel, rngLabel;
	minLabel = docdistrib.GetElementByID("globalMin");
	maxLabel = docdistrib.GetElementByID("globalMax");
	rngLabel = docdistrib.GetElementByID("globalRange");
	### set global value labels
	minLabel.SetText(sprintf("%d", minRast));
	maxLabel.SetText(sprintf("%d", maxRast));
	rngLabel.SetText(sprintf("%d", globalRange));
	### set the Distribution Type label
	class XMLNODE distType;		# class instance for label for Distribution type
	distType = docdistrib.GetElementByID("distType");
	if (distribType == 0) then
		distType.SetText( sprintf("Equal Range (%.1f)", intervalWidth) );
	else
		distType.SetText( sprintf("Equal Count (%d)", targetCount) );
	### get the id for the pane that will contain a pane of controls for each interval 
	local class XMLNODE distribList;
	distribList = docdistrib.GetElementByID("distribList");
	# add horizontal pane and controls for each raster interval
	local class XMLNODE paneRI;	# reuseable class instance for new pane
	local class XMLNODE editMin, editMax;	# reuseable class instances for new editnumber field
	local class XMLNODE labelRng, labelCount, labelPct;	# reuseable class instances for labels for range, count, percentage
	local class XMLNODE colorbut;	# reuseable class instance for color button for row
	local string interval$; 
	for i = 1 to numInterval
		{
		local string interval$ = NumToStr(i);
#		printf("interval = %d, minRange = %d, maxRange = %d, range = %d, count = %d, cellPct = %.2f \n", i, minRange[i], maxRange[i], maxRange[i] - minRange[i] + 1, cellCount[i], cellPct[i]);
		# add horizontal pane to hold controls
		paneRI = distribList.NewChild( "pane");
		paneRI.SetAttribute("Orientation", "horizontal");
		# add editnumber control for minRange
		editMin = paneRI.NewChild("editnumber");
		editMin.SetAttribute("id", sprintf("editMin%s", interval$));
		editMin.SetAttribute("Width", "6");
		editMin.SetAttribute("MinVal", NumToStr(minRast));
		editMin.SetAttribute("MaxVal", NumToStr(maxRast));
		editMin.SetAttribute("Default", NumToStr(minRange[i]));
		editMin.SetAttribute("WidthGroup", "1");
		editMin.SetAttribute("HorizAlign", "Center");
		editMin.SetAttribute("Precision", "0");
		editMin.SetAttribute("OnChanged", sprintf("onChangeMin(this, %s)", interval$ ));
		if (i == 1) then
			editMin.SetAttribute("ReadOnly", "true");
		else
			editMin.SetAttribute("ReadOnly", "false");
		# add editnumber control for maxRange
		editMax = paneRI.NewChild("editnumber");
		editMax.SetAttribute("id", sprintf("editMax%s", interval$));
		editMax.SetAttribute("Width", "6");
		editMax.SetAttribute("MinVal", NumToStr(minRast));
		editMax.SetAttribute("MaxVal", NumToStr(maxRast));
		editMax.SetAttribute("Default", NumToStr(maxRange[i]));
		editMax.SetAttribute("WidthGroup", "2");
		editMin.SetAttribute("HorizAlign", "Center");
		editMax.SetAttribute("Precision", "0");
		editMax.SetAttribute("OnChanged", sprintf("onChangeMax(this, %s)", interval$ ));
		if (i == numInterval) then
			editMax.SetAttribute("ReadOnly", "true");
		else
			editMax.SetAttribute("ReadOnly", "false");
		# add label for range
		labelRng = paneRI.NewChild("label");
		labelRng.SetText(NumToStr(maxRange[i] - minRange[i] + 1));
		labelRng.SetAttribute("id", sprintf("labelRng%s", interval$));
		labelRng.SetAttribute("HorizAlign", "Center");
		labelRng.SetAttribute("TextAlign", "Right");
		labelRng.SetAttribute("WidthGroup", "3");
		# add label for count
		labelCount = paneRI.NewChild("label");
		labelCount.SetText( NumToStr(cellCount[i] ) );
		labelCount.SetAttribute("id", sprintf("labelCount%s", interval$));
		labelCount.SetAttribute("TextAlign", "Right");
		labelCount.SetAttribute("HorizAlign", "Center");
		labelCount.SetAttribute("WidthGroup", "4");
		# add label for percent
		labelPct = paneRI.NewChild("label");
		labelPct.SetText(sprintf("%.2f", cellPct[i]));
		labelPct.SetAttribute("id", sprintf("labelPct%s", interval$));
		labelPct.SetAttribute("WidthGroup", "5");
		labelPct.SetAttribute("TextAlign", "Right");
		labelPct.SetAttribute("HorizAlign", "Center");
		# add color button
		colorbut = paneRI.NewChild("colorbutton");
		colorbut.SetAttribute("id", sprintf("colorbut%s", interval$));
		colorbut.SetAttribute("WidthGroup", "6");
		colorbut.SetAttribute("HorizAlign", "Center");
		}
	### Get the dialog element from the parsed XML text and show error message if
	### the dialog element can't be found
	nodedistrib = docdistrib.GetElementByID("distribDlg"); 
	if (nodedistrib == 0)
		{
		PopupMessage("Could not find dialog node in XML document");
		Exit();
		}
	### Set the XMl dialog element as the source for the GUI_DLG class instance we
	### are using for the Distribution dialog window
	dlgDistrib.SetXMLNode(nodedistrib);
	### Open the dialog window as a modal dialog
	dlgDistrib.DoModal();
	} # end setUpIntervals()
#####################################################################
######################### Main program ##############################
#####################################################################
clear();
##############################################################
### Standard colors for initial color palette.
### Can be modified using color buttons in Distribution dialog. 
xmlPalette$ = '<?xml version="1.0"?>
<palette name="Raster Interval Palette">
	<color id="1" red="0" green="0" blue="100"/>
   <color id="2" red="0" green="100" blue="0"/>
	<color id="3" red="100" green="0" blue="0"/>
	<color id="4" red="0" green="100" blue="100"/>
	<color id="5" red="100" green="0" blue="100"/>
	<color id="6" red="100" green="100" blue="0"/>
	<color id="7" red="0" green="0" blue="72"/>
	<color id="8" red="0" green="100" blue="72"/>
	<color id="9" red="0" green="25" blue="0"/>
	<color id="10" red="0" green="72" blue="100"/>
	<color id="11" red="0" green="44" blue="44"/>
	<color id="12" red="100" green="72" blue="72"/>
	<color id="13" red="72" green="0" blue="0"/>
	<color id="14" red="72" green="0" blue="100"/>
	<color id="15" red="44" green="0" blue="0"/>
	<color id="16" red="72" green="100" blue="72"/>
	<color id="17" red="72" green="72" blue="0"/>
	<color id="18" red="100" green="100" blue="67"/>
	<color id="19" red="0" green="72" blue="0"/>
	<color id="20" red="0" green="0" blue="44"/>
</palette>';
### parse XML text for color palette into memory; returns error code (number < 0)
### if there are syntax errors
errXML = docColor.Parse(xmlPalette$);
if ( errXML < 0 ) {
	PopupError( errXML );	# pop up an error dialog
	Exit( );
	}
#################################################
### Set up main dialog window
### Create string variable with XML specification of main
### dialog enclosed in single quotes (for multiline text)
xmlmain$ = '<?xml version="1.0"?>
<root>
	<dialog id="dlgmain" Title="Raster Intervals" Buttons="" OnOpen="onOpenMain()">
		<groupbox ExtraBorder="3">
			<label>Select a grayscale integer raster for the input raster:</label>
			<pane Orientation="horizontal">
				<pushbutton Name="Input Raster..." OnPressed="getRastIn()"/>
				<edittext id="inRastText" width="40" ReadOnly="True"/>
			</pane>
			<pane Orientation="horizontal" HorizAlign="Left" HorizResize="Fixed">
				<label>Number of intervals (maximum 20):</label>
				<editnumber id="numberIntervals" Default="5" MinVal="2" MaxVal="30" Precision="0" Width="2"/> 
				<label>Distribution:</label>
				<combobox id="distCombo" Default="0">
					<item Value="0">Equal Range</item>
					<item Value="1">Equal Count</item>
				</combobox>
			</pane>
		</groupbox>
		<pane Orientation="horizontal" HorizResize="Fixed" HorizAlign="Right">
			<pushbutton id="runButton" Name="Run..." Enabled="false" OnPressed="setUpIntervals()"/>
			<pushbutton Name="Exit" OnPressed="onExit()"/>
		</pane>
	</dialog>
</root>';
### parse XML text for main dialog into memory; returns error code (number < 0)
### if there are syntax errors
errXML = docmain.Parse(xmlmain$);
if ( errXML < 0 ) {
	PopupError( errXML );	# pop up an error dialog
	Exit( );
	}
### get the dialog element from the parsed XML text and show error
### message if the dialog element can't be found
nodemain = docmain.GetElementByID( "dlgmain" );
if ( nodemain == 0 ) {
	PopupMessage( "Could not find main dialog node in XML document" );
	Exit();
	}
### set the XML dialog element as the source for the GUI_DLG class instance
### we are using for the main dialog window
dlgmain.SetXMLNode(nodemain);
dlgmain.DoModal();