MapUnitAreas.sml

  Download

More scripts: Advanced

Syntax Highlighing:

comments, key words, predefined symbols, class members & methods, functions & classes
            
# MapUnitAreas.sml
## standalone script to tabulate areas of different map units occurring in contributing areas of catchments, using
## data produced by SampleCatchments.sml.
##### Inputs:
#	1) relocated sample point vector and sample catchment polygon vector objects created by SampleCatchments.sml
# 2) an overlapping geologic, soil, or other map with polygons attributed with a map unit identifier.
##### Outputs:
#	1) Catchment vector with UnitAreas table tabulating map unit areas and
#		area percentages for each catchment (summed over all contributing catchments);
#		records in this table are directly attached to the catchment polygons
#	2) Sample point vector with copy of the UnitAreas table with records directly
#		attached to the sample points
#	3) Intersect vector: logical intersect (VectorAND) of the catchment polygons and map
#		unit polygons, used to tabulate the map unit areas for each catchment.
# The UnitAreas tables include 
# -- CatchmentArea field that has area (sq Km) of all contributing catchment polygons 
# 		(computed for each catchment polygon by SampleCatchments.sml and read from PolyToPoly table)
# -- TotalArea_sqkm field that shows summed area of all map unit subpolygons found in the Intersect vector
#			for the current catchment polygon and its contributing catchment polygons; should equal CatchmentArea value
# -- TotalUnitPct field = 100 * TotalArea_sqkm / CatchmentArea; should equal 100% (check for correct processing)
##### Custom interface window allows user to:
#	-- choose input catchment and sample point vectors
# -- choose the table and field from the sample point vector that has the unique sample ID
# -- choose the table and field from the map unit vector that has the map unit identifiers
# -- optionally choose a particular target map unit to compile areas, otherwise they are
#			compiled for all map units.
# -- choose Project File for output vectors, which are automatically named: catchment and sample point
# 		vectors get input name + "areas"; Intersect vector named "Intersect".
### Script checks:  #######
# Sample point vector:
#	1) has point elements
# 2) has a table named PointToPoint created by SampleCatchements.sml
# 3) the sample  ID field selected by user is either string or numeric
# Catchment polygon vector:
# 1) has polygons
# 2) has a table named PolyToPoly created by SampleCatchments.sml
# 3) has a table with the same name as the sample ID table selected by user from the sample point vector
# Map unit vector 
# 1) has polygons
# 2) the map unit ID field selected by user is either string or numeric
# 3) is reprojected if necessary to match the CRS of the catchment vector to ensure proper result from intersect operation.
###  March 23, 2009
###  Randy Smith
###  MicroImages, Inc.
### Revised 2 July 2009: changed dialog from modal to modeless, enabling multiple runs with same data (must
			# choose new output Project File). 
###########  Global Variable Declarations  ###################
class RVC_VECTOR InSampPtVec, InCatchPolyVec, MapVec;
class RVC_VECTOR OutSampPtVec, OutCatchPolyVec, IntersectVec;
class SR_COORDREFSYS catchCRS, mapCRS;
class RVC_OBJITEM inSampPtObjItem, inCatchPolyObjItem, MapUnitObjItem;
string sampTblName$, sampFieldName$, mapUnitTblName$, mapUnitFieldName$;
class FILEPATH inputFilepath;
class STRING targetUnitStr = "";
class STRING sampIdFieldTypeStr, mapUnitIdFieldTypeStr;
class STRING outProjFilename$;
class DATABASE inCatchPolyDB;			# polygon database for input catchment vector
class DBTABLEINFO mapTbl, catchUnitAreaTbl, sampUnitAreaTbl;
class STRINGLIST mapUnitList;		# stringlist with single entry for each map unit
class STRINGLIST catchmentIDlist;		# stringlist with IDs for current catchment polygon and its upstream catchments 
string message$;
### Variables for the script dialog and controls
numeric err;		# for error handling
class STRING xml$;
class XMLDOC dlgdoc;
class XMLNODE dlgnode;
class GUI_DLG dlgwin;
class GUI_CTRL_PUSHBUTTON runBtn;
class GUI_CTRL_COMBOBOX targetUnitCombo;
class GUI_CTRL_EDIT_STRING ptVecText, polyVecText, mapVecText;
class GUI_CTRL_EDIT_STRING outProjFileText, targetUnitText, statusText;
class GUI_CTRL_LABEL targetUnitLabel;
####################################################
###########  User-defined procedures and functions  #############
####################################################
### function to return stringlist with single entry for each map unit identifier
func class STRINGLIST getMapUnitList (class DBTABLEINFO dbInfo, class STRING fieldName$)
	{
	local numeric i, j;							# counters
	local numeric unitInList;			# flag to indicate whether unit has been found in list
	local class STRING unit$;
	local class STRINGLIST list;
#	print("getMapUnitList function called.");
#	printf("Number of records in table = %d\n", dbInfo.NumRecords);
#	printf("mapUnitIdFieldTypeStr = %s\n", mapUnitIdFieldTypeStr);
#	printf("mapUnitFieldName$ = %s\n", mapUnitFieldName$);
	for i = 1 to dbInfo.NumRecords
		{
		# read map unit identifier field in record 
		if (mapUnitIdFieldTypeStr == "string")
			{
			unit$ = TableReadFieldStr(dbInfo, fieldName$, i);
			}
		else
			unit$ = NumToStr(TableReadFieldNum(dbInfo, fieldName$, i) );
		if (unit$ <> "")	# if string is not empty
			{
			# if stringlist is empty, add the unit identifier to the list
			if (list.GetNumItems() == 0) then
				list.AddToEnd(unit$);
			# otherwise loop through the stringlist to see if unit identifier is already in list
			else
				{
				unitInList = 0;
				for j = 1 to list.GetNumItems()
					{
					if (list[j - 1] == unit$) then unitInList = 1;
					}
				if (unitInList == 0) then
					list.AddToEnd(unit$);
				}		# end else
			}  # end if (unit$ <> "")
		}  # end for i = 1 to info.NumRecords
		list.Sort();		# sort list alphabetically
	return list;
	}  # end getMapUnitList()
####################################################
### function to return stringlist with IDs for current catchment and its upstream catchments
proc getUpstreamIDs
	(class STRING IDstr, 						# id string
	class STRING sampFld$,				# sample field name
	class DBTABLEINFO sampTbl, 	# sample table in the output point vector
	class DBTABLEINFO ptpTbl )		# PointToPoint table in the output point vector
	{
	local numeric j;		# counter
	local string fieldbase$ = "UpSample";		# base string for numbered UpSample fields
	local class STRINGLIST tempList;
	# add current sample ID to global ID List;
	catchmentIDlist.AddToEnd(IDstr);
	# get record number in the point sample table for the current sample ID;
	# this table is directly attached to the point elements
	local numeric recNum = TableKeyFieldLookup(sampTbl, sampFld$, IDstr);
	# get the element number of the sample point this record is attached to
	local array numeric ptNums[1];			# array of element numbers; should be just one
	TableGetRecordElementList(sampTbl, recNum, ptNums, "point");  
	# get the record in the PointToPoint table that is attached to this point
	local array numeric recNums[1];		# array of record numbers for attached records; should be just 1
	TableReadAttachment(ptpTbl, ptNums[1], recNums, "point");
	# get number of upstream samples immediately adjacent to the current basin from PointToPoint table
	local numeric numAdjUp = TableReadFieldNum(ptpTbl, "NumUpSamples", recNums[1]);
#	printf("Number of upstream samples = %d\n", numAdjUp);
	if (numAdjUp > 0)	# if there are adjacent upstream polygons
		{
		# get list of sample IDs for adjacent upstream samples from UpSample[num] fields 
		# in the PolyToPoly table
		for j = 1 to numAdjUp
			{
			# create string for field name holding the appropriate UpSample basin ID
			# and add to local stringlist
			local string field$ = fieldbase$ + NumToStr(j);
			local class STRING tempIDstr;
			# read ID from the current upstream field
			if (sampIdFieldTypeStr == "string") then 
				tempIDstr = TableReadFieldStr(ptpTbl, field$, recNums[1] );
			else
				tempIDstr = NumToStr( TableReadFieldNum(ptpTbl, field$, recNums[1] ) );
			tempList.AddToEnd(tempIDstr);		# add ID as string to temporary stringlist
			}
		# loop through temporary list of UpSample fields to get their adjacent upstream basin IDs
		# and call this function to check for basins further upstream
		for j =1 to numAdjUp
			{ 
			getUpstreamIDs( tempList[j-1], sampFld$, sampTbl, ptpTbl);
			}
		}	# end processing upstream samples / polygons
	} # end getUpstreamIDs( )
##########  User-defined procedures for the dialog window  #########
### procedure called by Catchment Poly Vector pushbutton
proc OnCatchBtn ()
	{
	local string message$;
	local numeric failed;
	local class RVC_GEOREFERENCE catchGeoref;
	err = DlgGetObject("Select USDBASIN vector object with catchment polygons:", "Vector", inCatchPolyObjItem, "ExistingOnly"); 
	if (err == 0)
		{
		InCatchPolyVec.Open(inCatchPolyObjItem, "Read");		# open the catchment vector to check database tables
		# check that selected vector has polygons
		if (InCatchPolyVec.$Info.NumPolys == 0)
			{
			failed = 1;
			message$ = "Selected vector does not have polygon elements.\n";
			}
		else
			{
			# check that catchment vector has a table named PolyToPoly from SampleCatchments script
			inCatchPolyDB =OpenVectorPolyDatabase(InCatchPolyVec); 
			if (!TableExists(inCatchPolyDB, "PolyToPoly") )
				{
				failed = 1;
				message$ = "Selected catchment vector does not have the required PolyToPoly table from SampleCatchments.sml.\n";
				}
			else
				{
				# get georeference and coordinate reference system from the catchment vector
				InCatchPolyVec.GetDefaultGeoref(catchGeoref);
				catchCRS = catchGeoref.GetCoordRefSys();
				polyVecText.SetValueStr(inCatchPolyObjItem.GetDisplayString() );
				dlgwin.GetCtrlByID("inSampBtn").SetEnabled(1);			# enable the Sample Point Vector button
				}
			}
		if (failed > 0)
			{
			message$ += "Please select a valid catchment vector object.";
			PopupMessage(message$);
			polyVecText.SetValueStr("");
			return;
			}
		}
	}
### procedure called by Sample Point Vector pushbutton
proc OnSamplePtBtn ()
	{
	local numeric failed;
	err = DlgGetObject("Select Result vector object with relocated sample points", "Vector", inSampPtObjItem, "ExistingOnly");
	if (err == 0)
		{
		InSampPtVec.Open(inSampPtObjItem, "Read");		# open the vector object and show info in dialog
		# check that selected vector has point elements.
		if (InSampPtVec.$Info.NumPoints == 0)		# no point elements; show error message
			{
			failed = 1;
			message$ = "The selected vector object has no point elements.\n";
			}
		else						# selected vector has point elements
			{
			local class DATABASE ptDB;		# get the point database
			ptDB = OpenVectorPointDatabase(InSampPtVec);
			# check that selected vector has PointToPoint table from SampleCatchments.sml
			if (!TableExists(ptDB, "PointToPoint") )
				{
				failed = 1;
				message$ = "The selected vector does not have the required PointToPoint table from SampleCatchments.sml.\n";
				}
			else
				{
				# set name of selected vector in dialog
				ptVecText.SetValueStr(inSampPtObjItem.GetDisplayString() );
				inputFilepath = InSampPtVec.$Info.Filename;
				# prompt to select table.field with sample IDs
				PopupMessage("Please select sample point table and field containing the unique sample ID.");
				PopupSelectTableField(ptDB, sampTblName$, sampFieldName$);
				if (sampTblName$ == "" or sampFieldName$ == "")
					{
					message$ = "The table and field for the unique sample ID have not been specified.\n";
					message$ += "Please reselect the Result vector and select the sample ID table and field.";
					PopupMessage(message$);
					ptVecText.SetValueStr("");
					return;
					}
				# check that the previously selected catchment polygon vector has a table with the same name
				if (!TableExists(inCatchPolyDB, sampTblName$) )
					{
					message$ = sprintf("The previously-selected catchment vector object does not have a table named %s.\n", sampTblName$);
					message$ += "Please select a different table for the sample id or select a catchment vector that includes this table.";
					PopupMessage(message$);
					}
				else		# catchment vector has a sample table with the same name
					{
					# get table info and field info to get field type for the sample ID field (numeric or string);
					local class DBTABLEINFO sampTblInfo;
					sampTblInfo = DatabaseGetTableInfo(ptDB, sampTblName$);
					local class DBFIELDINFO sampFieldInfo;
					sampFieldInfo =FieldGetInfoByName(sampTblInfo, sampFieldName$);
					sampIdFieldTypeStr =sampFieldInfo.Type;
					# check that selected ID field is string or numeric
					if (sampIdFieldTypeStr == "string" or sampIdFieldTypeStr == "numeric")
						{
						# show selected table and field names in dialog
						dlgwin.GetCtrlByID("sampTableLabel").SetEnabled(1);
						dlgwin.GetCtrlByID("sampTableText").SetValueStr(sampTblName$);
						dlgwin.GetCtrlByID("sampFieldLabel").SetEnabled(1);
						dlgwin.GetCtrlByID("sampFieldText").SetValueStr(sampFieldName$);
						dlgwin.GetCtrlByID("mapUnitBtn").SetEnabled(1);		# enable the Map Unit Vector button
						}
					else
						{
						message$ = "Sample ID field must be string or numeric.\n";
						message$ += "Please reselect the Result vector and select the sample ID table and field.";
						PopupMessage(message$);
						ptVecText.SetValueStr("");
						return;
						}
					} # end else polygon sample table exists
				} # end else point vector has PointToPoint table
			}
		if (failed > 0)
			{
			message$ += "Please select a valid sample point result vector object.";
			PopupMessage(message$);
			return;
			}
		}
	}
### procedure called by Map Unit Vector pushbutton
proc OnMapUnitBtn ()
	{
	local class RVC_GEOREFERENCE mapGeoref;
	local string message$;
	err = DlgGetObject("Select vector object with map unit polygons:", "Vector", MapUnitObjItem, "ExistingOnly");
	if (err == 0)
		{
		MapVec.Open(MapUnitObjItem, "Read");	# open the vector object and show info in dialog
		mapVecText.SetValueStr(MapUnitObjItem.GetDisplayString() );
		local class DATABASE mapUnitDB;	# prompt to select table.field with map unit IDs
		mapUnitDB = OpenVectorPolyDatabase(MapVec);
		PopupMessage("Please select the table and field containing the unique map unit ID.");
		PopupSelectTableField(mapUnitDB, 	mapUnitTblName$, mapUnitFieldName$);
		# check for Table Field dialog being canceled.
		if (mapUnitTblName$ == "" or mapUnitFieldName$ == "")
			{
			message$ = "The table and field for the map unit identifier have not been specified.\n";
			message$ += "Please reselect the Map Unit vector and select the map unit ID table and field.";
			PopupMessage(message$);
			mapVecText.SetValueStr("");
			return;
			}
		# get table info and field info to get field type for the map unit ID field (numeric or string);
		mapTbl = DatabaseGetTableInfo(mapUnitDB, mapUnitTblName$);
		local class DBFIELDINFO mapFieldInfo;
		mapFieldInfo =FieldGetInfoByName(mapTbl, mapUnitFieldName$);
		mapUnitIdFieldTypeStr =mapFieldInfo.Type;
		# check that selected ID field is string or numeric
		if (mapUnitIdFieldTypeStr == "string" or mapUnitIdFieldTypeStr == "numeric")
			{
			MapVec.GetDefaultGeoref(mapGeoref);
			mapCRS = mapGeoref.GetCoordRefSys();
			# show selected table and field names in dialog
			dlgwin.GetCtrlByID("mapTableLabel").SetEnabled(1);
			dlgwin.GetCtrlByID("mapTableText").SetValueStr(mapUnitTblName$);
			dlgwin.GetCtrlByID("mapFieldLabel").SetEnabled(1);
			dlgwin.GetCtrlByID("mapFieldText").SetValueStr(mapUnitFieldName$);
			# get list of map unit identifiers
			mapUnitList = getMapUnitList(	mapTbl, mapUnitFieldName$);
			dlgwin.GetCtrlByID("targetToggle").SetEnabled(1);		# enable toggle for single map unit
			dlgwin.GetCtrlByID("outProjFileBtn").SetEnabled(1);		# enable Output Project File button
			}
		else
			{
			message$ = "Map Unit ID field must be string or numeric.\n";
			message$ += "Please reselect the Map Unit vector and select the map unit ID table and field.";
			PopupMessage(message$);
			mapVecText.SetValueStr("");
			return;
			}
		}
	}
### procedure called by toggle button to tabulate areas for single map unit
proc OnTargetToggle ()
	{
	local numeric i;		# counter
	# get handles for map unit label and text field
	targetUnitLabel = dlgwin.GetCtrlByID("targetUnitLabel");
	targetUnitText = dlgwin.GetCtrlByID("targetUnitText");
	if (targetUnitStr == "")			# toggle was off, no target map unit string had been set
		{
		targetUnitLabel.SetEnabled(1);
		# get handle for the map unit combobox, populate the combobox, and enable it
		targetUnitCombo = dlgwin.GetCtrlByID("targetUnitCombo");
		for i = 1 to mapUnitList.GetNumItems()
			{
			targetUnitCombo.AddItem(mapUnitList[i-1], mapUnitList[i-1]);
			}
		targetUnitCombo.SetSelectedItemID( mapUnitList[0] );
		targetUnitCombo.SetEnabled(1);
		targetUnitStr = targetUnitCombo.GetSelectedItemID();
		}
	else													# toggle was on, target map unit string had been set, 
		{
		targetUnitStr = "";			# set blank target map unit string
		targetUnitLabel.SetEnabled(0);
		targetUnitText.SetEnabled(0);
		}
	}
### procedure called when target map unit is selected
proc OnMapUnitSelected ()
	{
	targetUnitStr = targetUnitCombo.GetSelectedItemID();
	}
### procedure called when Output Project File button is pressed
proc OnOutputProjFileBtn ()
	{
	local string message$;
	local string startDir$ = inputFilepath.GetPath();
	outProjFilename$ = GetOutputFileName(startDir$, "Select Project File to save output objects to:", "rvc");
	# check if user pressed "Cancel" in the selection dialog
	if (outProjFilename$ == startDir$)
		{
		message$ = "No output Project File name selected.\n";
		message$ += "Please select an output Project File.";
		PopupMessage(message$);
		return;
		}
	outProjFileText.SetValueStr(outProjFilename$);
	runBtn.SetEnabled(1);
	# Delete the file if it exists.  The user would have received a "Overwrite this file?" warning.
	if (fexists(outProjFilename$) )
		DeleteFile(outProjFilename$);
	CreateProjectFile(outProjFilename$, "Output objects from MapUnitAreas SML script");
	statusText.SetValueStr("");
	}
### procedure called when Close button is pressed
proc onClose ()
	{
	dlgwin.Close(0);
	Exit();
	}
#######################################
### procedure called when Run button is pressed
proc onRun ()
	{
	local numeric i, j, k;			# counters
	local class STRING areaFldStr, pctFldStr, idStr, mapUnitStr;
	local numeric id;
	local numeric catchmentArea, unitArea, sumUnitAreas;
	local numeric recNum, numAttached;
	local array numeric recordArray[1];
	local array numeric elemArray[1];
	clear();			# clear console
	############  CATCHMENT POLYGON VECTOR  ########
	# Make copy of catchment polygon vector in output Project File
	print("Making copy of catchment polygon vector.");
	statusText.SetValueStr("Making copy of catchment polygon vector.");
	CreateVector(OutCatchPolyVec,  outProjFilename$, "USDBASINareas", "Catchment polygons with tabulated map unit areas", "Polygonal");
	VectorCopyElements(InCatchPolyVec, OutCatchPolyVec);
	CloseVector(InCatchPolyVec);
	# open output catchment vector's polygon database
	class DATABASE catchPolyDB;
	catchPolyDB = OpenVectorPolyDatabase(OutCatchPolyVec);
	# get the PolyToPoly table info and info for the sample ID field 
	class DBTABLEINFO polyToPoly;
	polyToPoly = DatabaseGetTableInfo(catchPolyDB, sampTblName$);
	class DBFIELDINFO sampID_PtoP;
	sampID_PtoP = FieldGetInfoByName(polyToPoly, sampFieldName$); 
	# get the sample table info and info for the sample ID field to set as the key
	# for foreign key field in new unit area table
	class DBTABLEINFO outCatchSampTbl;
	outCatchSampTbl = DatabaseGetTableInfo(catchPolyDB, sampTblName$);
	class DBFIELDINFO sampID_ocSampTbl;
	##############  SAMPLE POINT VECTOR  ################
	# Make copy of relocated sample point vector in output Project File
	print("Making copy of sample point vector.");
	statusText.SetValueStr("Making copy of sample point vector.");
	CreateVector(OutSampPtVec, outProjFilename$, "SampPTareas", "Relocated sample points with tabulated map unit areas.", "Planar"); 
	VectorCopyElements(InSampPtVec, OutSampPtVec);
	CloseVector(InSampPtVec);
	# open the point database of the sample point vector and get info
	# for the PointToPoint table and sample table
	local class DATABASE sampPtDB;
	sampPtDB = OpenVectorPointDatabase(OutSampPtVec);
	local class DBTABLEINFO sampPtToPtTbl, sampPtTbl;
	sampPtToPtTbl = DatabaseGetTableInfo(sampPtDB, "PointToPoint");
	sampPtTbl = DatabaseGetTableInfo(sampPtDB, sampTblName$);
	#############   MAP UNIT VECTOR   ###################
	# compare CRS of catchment and map unit vectors and reproject
	# map unit vector to match if necessary.
	class RVC_VECTOR MapUnitVec;
	CreateTempVector(MapUnitVec, "Polygonal");
	if (mapCRS.Name <> catchCRS.Name)
		{
		print("Reprojecting the map unit vector to match the catchment vector.");
		statusText.SetValueStr("Reprojecting the map unit vector to match the catchment vector.");
		local class TRANS2D_MAPGEN trans;
		trans.InputCoordRefSys = mapCRS;
		trans.OutputCoordRefSys = catchCRS;
		VectorWarp(MapVec, MapUnitVec, trans, 10);
		}
	else
		VectorCopyElements(MapVec, MapUnitVec);
	#############  INTERSECT VECTOR  ###############
	# Do Logical Intersect ("AND") with map unit vector (source) and catchment polygon vector (operator)
	# Result is vector with basin polygons subdivided by map unit, with original basin and map unit records
	#    attached to the subpolygons.
	print("Doing logical Intersect of map unit vector and catchment polygon vector.");
	statusText.SetValueStr("Doing logical Intersect of map unit vector and catchment polygon vector.");
	CreateVector(IntersectVec, outProjFilename$, "Intersect", "Intersect of catchment polygons and map unit polygons.");
	IntersectVec = VectorAND(OutCatchPolyVec, MapUnitVec);
	# update the standard attributes for the polygons in the Intersect vector (to compute areas)
	VectorToolkitInit(IntersectVec);
	VectorUpdateStdAttributes(IntersectVec);
	# open the Intersect vector's polygon database and get handles for its sample, map unit,
	# and POLYSTATS tables
	class DATABASE intersectPolyDB;
	intersectPolyDB = OpenVectorPolyDatabase(IntersectVec); 
	class DBTABLEINFO intersectSampTbl, intersectMapUnitTbl, intersectPolystatsTbl;
	intersectSampTbl = DatabaseGetTableInfo(intersectPolyDB, sampTblName$);
	intersectMapUnitTbl = DatabaseGetTableInfo(intersectPolyDB, mapUnitTblName$);
	intersectPolystatsTbl = DatabaseGetTableInfo(intersectPolyDB, "POLYSTATS");
	############  CATCHMENT UNIT AREA TABLE  ################
	# make new unit area polygon table for output catchment vector
	print("Making unit area polygon table for the output catchment vector.");
	statusText.SetValueStr("Making unit area polygon table for the output catchment vector.");
	class DBTABLEINFO  catchUnitAreaTbl;
	catchUnitAreaTbl = TableCreate(catchPolyDB, "UnitAreas", "Map unit areas and cumulative areas for catchments");
	catchUnitAreaTbl.OneToOneAttachment = 1;
	# create field for sample ID 
	class DBFIELDINFO sampIDcatchFld;
	if (sampIdFieldTypeStr == "string")
		sampIDcatchFld = TableAddFieldString(catchUnitAreaTbl, sampFieldName$, 30);
	else
		sampIDcatchFld = TableAddFieldInteger(catchUnitAreaTbl, sampFieldName$, 30);
	# add CatchmentArea field to verify if search of upstream IDs succeeded
	class DBFIELDINFO checkArea;
	checkArea = TableAddFieldFloat(catchUnitAreaTbl, "CatchmentArea", 10, 4);
	checkArea.UnitType = "Area";
	checkArea.Units = "square kilometers";
	# add fields for area and percent area for each map unit
	class DBFIELDINFO areaFld, pctFld;
	if (targetUnitStr == "")			# no target map unit selected; add fields for all map units
		{
		message$ = sprintf("Making fields for %d map units\n", mapUnitList.GetNumItems());
		print(message$);
		statusText.SetValueStr(message$);
		for i = 1 to mapUnitList.GetNumItems()
			{
			areaFldStr = mapUnitList[i-1] + "_Area"; 
			areaFld = TableAddFieldFloat(catchUnitAreaTbl, areaFldStr, 10, 4);
			areaFld.UnitType = "Area";
			areaFld.Units = "square kilometers";
			pctFldStr = mapUnitList[i-1] + "_Pct";
			pctFld = TableAddFieldFloat(catchUnitAreaTbl, pctFldStr, 10, 1);
			}
		}
	else					# user selected a target map unit; make fields for only this unit
		{
		print("Making fields for the target map unit.");
		statusText.SetValueStr("Making fields for the target map unit.");	
		areaFldStr = targetUnitStr + "_Area";
		areaFld = TableAddFieldFloat(catchUnitAreaTbl, areaFldStr, 10, 4);
		areaFld.UnitType = "Area";
		areaFld.Units = "square kilometers";
		pctFldStr = targetUnitStr + "_Pct";
		pctFld = TableAddFieldFloat(catchUnitAreaTbl, pctFldStr, 10, 1);
		}
	# add fields for area and percent area for all map units as another check
	class DBFIELDINFO totAreaFld, totPctFld;
	totAreaFld = TableAddFieldFloat(catchUnitAreaTbl, "TotalArea_sqkm", 10, 4);
	totAreaFld.UnitType ="Area";
	totAreaFld.Units = "square kilometers";
	totPctFld = TableAddFieldFloat(catchUnitAreaTbl, "TotalUnitPct", 10, 1);
	#############  ADD RECORDS WITH IDS  ######################
	# add a record to the unit area polygon table for each polygon in output catchment vector
	# and attach to the polygons
	print("Adding records to unit area polygon table.");
	statusText.SetValueStr("Adding records to unit area polygon table.");
	for i = 1 to polyToPoly.NumRecords
		{
		# get the polygon attached to the current PolyToPoly record
		numAttached = TableGetRecordElementList(polyToPoly, i, elemArray);
		# get the value from the TotalArea_sq_km field in the PolyToPoly table;
			# this is the total area of all contributing catchment polygons
		catchmentArea = TableReadFieldNum(polyToPoly, "TotalArea_sq_km", i);
		# get sample ID for the current record in the PolyToPoly table and
		# write new record to unit area polygon table with ID and total area values
		if (sampIdFieldTypeStr	=="string")
			{
			idStr = TableReadFieldStr(polyToPoly, sampFieldName$, i);
			recNum = TableNewRecord(catchUnitAreaTbl, idStr, catchmentArea);
			}
		else
			{
			id = TableReadFieldNum(polyToPoly,  sampFieldName$, i);
			recNum = TableNewRecord(catchUnitAreaTbl, id, catchmentArea);
			}
		recordArray[1] = recNum;
		TableWriteAttachment(catchUnitAreaTbl, elemArray[1], recordArray, 1, "polygon");
		}
	############  COMPUTE UNIT AREAS  ####################### 
	local numeric areaSqKm, unitPct, totalAreaPct;
	print("Computing map unit areas.");
	statusText.SetValueStr("Computing map unit areas.");
	if (targetUnitStr == "")		# if no target unit, computing areas for all map units
		numeric unitAreas[];				# set up hash to compile areas for map units (unit IDs used as keys to hash)
	else
		local numeric sumTargetAreas;		# simple numeric variable to accumulate areas for target unit
	# loop through records in unit area polygon table and sum total contributing
	# area for each basin (as check) and areas of each map unit in contributing
	# area, then compute percentage for each unit
	for i = 1 to catchUnitAreaTbl.NumRecords
		{
		# get sample ID for current record in the unit area polygon table as a string
		if (sampIdFieldTypeStr	=="string")
			idStr = TableReadFieldStr(catchUnitAreaTbl, sampFieldName$, i); 
		else
			idStr = NumToStr( TableReadFieldNum(catchUnitAreaTbl, sampFieldName$, i) );
		# call recursive function to get stringlist of all upstream sample/catchment IDs
		getUpstreamIDs(idStr, sampFieldName$, sampPtTbl, sampPtToPtTbl);
		#######################
		# loop through stringlist of contributing catchment IDs for the current catchment
		for j = 1 to catchmentIDlist.GetNumItems()
			{
			# get the record number for the current ID in the Intersect Vector sample table;
				# this record is attached to all unit (sub)polygons for the current catchment
			recNum = TableKeyFieldLookup(intersectSampTbl, sampFieldName$, catchmentIDlist[j-1]);
			# get element numbers of all (sub)polygons this sample number is attached to
			numAttached = TableGetRecordElementList(intersectSampTbl, recNum, elemArray);
			######################
			# loop through the intersect subpolygons to compile polygon areas and unit areas
			for k = 1 to numAttached
				{
				# get number of attached record in the Intersect Vector POLYSTATS table
				TableReadAttachment(intersectPolystatsTbl, 	elemArray[k], recordArray);
				# get the area from the POLYSTATS table and add to the running sum of subpolygon areas
				unitArea = TableReadFieldNum(intersectPolystatsTbl, "Area", recordArray[1] );
				sumUnitAreas +=unitArea;
				ResizeArrayClear(recordArray, 1);		# clear the record array
				# get number of attached record in the IntersectVector map unit table
				TableReadAttachment(intersectMapUnitTbl, elemArray[k], recordArray);
				# read map unit identifier field in record 
				if (mapUnitIdFieldTypeStr == "string")
					mapUnitStr = TableReadFieldStr(intersectMapUnitTbl, mapUnitFieldName$, recordArray[1]);
				else
					mapUnitStr = NumToStr(TableReadFieldNum(intersectMapUnitTbl, mapUnitFieldName$, recordArray[1]) );
				ResizeArrayClear(recordArray, 1);
				if (targetUnitStr == "")			# no target unit specified, add area for the current unit to its index in the unit hash
					unitAreas[mapUnitStr] += unitArea;
				else			# record area only if polygon matches target unit
					if (mapUnitStr == targetUnitStr)  then  sumTargetAreas += unitArea;
				} # end of loop through intersect subpolygons
				# clear the array of intersect polygon element numbers for the next pass
				ResizeArrayClear(elemArray, 0);
			}	# end of loop through stringlist of contributing catchment IDs
			catchmentIDlist.Clear();		# clear the stringlist of catchment IDs for the next pass
			# convert summed unit areas from square meters to square km
			sumUnitAreas = sumUnitAreas / 1000000;
		######################################
		# write computed results for the current catchment to appropriate fields in the 
		# output catchment vector's unit area table
		if 	(targetUnitStr == "")		# write area and percentage values for each map unit to the record
			{
			# loop through map unit stringlist to get summed unit areas for the current catchment
			for k = 1 to mapUnitList.GetNumItems()
				{
				# get summed area for the current map unit from the area hash and convert to square km
				areaSqKm = unitAreas[ mapUnitList[k-1] ] / 1000000;
				# write area if value is not null
				if (!IsNull(areaSqKm) )
					{
					unitPct = 100 * areaSqKm / sumUnitAreas;
					TableWriteField(catchUnitAreaTbl, i, sprintf("%s_Area",  	mapUnitList[k-1] ), areaSqKm);
					TableWriteField(catchUnitAreaTbl, i, sprintf("%s_Pct", mapUnitList[k-1] ), unitPct);
					}
				}
			} # end writing info for all map units
		else			# write area and percentage values for just the target map unit
			{
			areaSqKm = sumTargetAreas / 1000000;
			# write area if value is not null
			if ( !IsNull(areaSqKm) )
				{
				unitPct = 100 * areaSqKm / sumUnitAreas;
				TableWriteField(catchUnitAreaTbl, i, sprintf("%s_Area", targetUnitStr), areaSqKm);
				TableWriteField(catchUnitAreaTbl, i, sprintf("%s_Pct", targetUnitStr), unitPct);
				}
			}
		# compute total area percentage and write total unit area and percentage to current record
		totalAreaPct = 100 * sumUnitAreas / TableReadFieldNum(catchUnitAreaTbl, "CatchmentArea", i);
		TableWriteField(catchUnitAreaTbl, i, "TotalArea_sqkm", sumUnitAreas);
		TableWriteField(catchUnitAreaTbl, i, "TotalUnitPct", totalAreaPct);
		if (targetUnitStr == "")
			unitAreas.Clear();				# clear the unit area hash
		else
			sumTargetAreas = 0;			# reset the target area sum
		}	# end of computing unit areas
	print("Copying unit area table to the output sample point vector.");
	statusText.SetValueStr("Copying unit area table to the output sample point vector.");
	##################################################
	#######  UNIT AREA TABLE FOR OUTPUT SAMPLE POINT VECTOR#####
	# copy unit area table to the output sample point vector's point database
	TableCopy(catchPolyDB, catchUnitAreaTbl, sampPtDB);
	# get info for the new point table and for its sample ID field
	local class DBTABLEINFO ptUnitAreaTbl;
	ptUnitAreaTbl = DatabaseGetTableInfo(sampPtDB, "UnitAreas");
	local class DBFIELDINFO sampIDptUnitAreaTbl;
	sampIDptUnitAreaTbl = FieldGetInfoByName(ptUnitAreaTbl, sampFieldName$);
	# loop through records in point unit area table to attach records to points
	for i = 1 to ptUnitAreaTbl.NumRecords
		{ 
		# get the sample ID for the current record in the point unit area table and
		# find the corresponding record number in the sample point table
		if (sampIdFieldTypeStr	=="string")
			{
			idStr = TableReadFieldStr(ptUnitAreaTbl, sampFieldName$, i);
			recNum = TableKeyFieldLookup(sampPtTbl, sampFieldName$, idStr);
			}
		else
			{
			id = TableReadFieldNum(ptUnitAreaTbl,  sampFieldName$, i);
			recNum = TableKeyFieldLookup(sampPtTbl, sampFieldName$, id);
			}
		# get the point element number attached to this record in the sample point table
		numAttached = TableGetRecordElementList(sampPtTbl, recNum, elemArray);
		recordArray[1] = i;
		# attach the current record in the point unit area table to the corresponding point element	 
		TableWriteAttachment(ptUnitAreaTbl, elemArray[1], recordArray, 1);
		}
	print("Done.");
	statusText.SetValueStr("Done.");
	# disable Run button and clear output Project File; must select new output if want to run again
	runBtn.SetEnabled(0);
	outProjFileText.SetValueStr("");
	}		# end OnRun()
####################################################
###################   Main Program  ######################
# dialog specification
xml$ = '<?xml version="1.0"?>
<!DOCTYPE root SYSTEM "smlforms.dtd">
<root>
	<dialog id="mudlg" Title="Map Unit Areas"  Buttons="">
		<groupbox Name=" Select Input Vector Objects Created by SampleCatchments.sml: " ExtraBorder="4">
			<pane Orientation="horizontal">
				<pushbutton id="inCatchBtn" Name=" Catchment Poly Vector... " WidthGroup="1" OnPressed="OnCatchBtn()"/>
				<edittext id="polyVecText" Width="40" ReadOnly="true"/>
			</pane>
			<pane Orientation="horizontal">
				<pushbutton id="inSampBtn" Name=" Sample Point Vector... " WidthGroup="1" Enabled="false" OnPressed="OnSamplePtBtn ()"/>
				<edittext id="ptVecText" Width="40" ReadOnly="true"/>
			</pane>
			<pane Orientation="horizontal">
				<label id="sampTableLabel" Enabled="false">Sample Table Name </label>
				<edittext id="sampTableText" Width="15" ReadOnly="true"/>
				<label id="sampFieldLabel" Enabled="false">Unique ID Field </label>
				<edittext id="sampFieldText" Width="15" ReadOnly="true"/>
			</pane>
		</groupbox>
		<groupbox Name=" Select Map Unit Vector Object " ExtraBorder="4">
			<pane Orientation="horizontal">
				<pushbutton id="mapUnitBtn" Name=" Map Unit Vector... " WidthGroup="1" Enabled="false" OnPressed="OnMapUnitBtn()"/>
				<edittext id="mapVecText" Width="40" ReadOnly="true"/>
			</pane>
			<pane Orientation="horizontal">
				<label id="mapTableLabel" Enabled="false">Map Unit Table Name </label>
				<edittext id="mapTableText" Width="15" ReadOnly="true"/>
				<label id="mapFieldLabel" Enabled="false">  Map Unit ID Field </label>
				<edittext id="mapFieldText" Width="15" ReadOnly="true"/>
			</pane>
			<pane Orientation="horizontal">
				<togglebutton id="targetToggle" Name="Tabulate areas for single map unit    " Enabled="false" OnChanged="OnTargetToggle()"/>
				<label id="targetUnitLabel" Enabled="false">  Target map unit </label>
				<combobox id="targetUnitCombo" Width="10" Enabled="false" OnSelection="OnMapUnitSelected()"/>
			</pane>
		</groupbox>
		<pane Orientation="horizontal">
			<pushbutton id="outProjFileBtn" Name=" Output Project File... " Enabled="false" OnPressed="OnOutputProjFileBtn()"/>
			<edittext id="outProjFileText" Width="40" ReadOnly="true" />
		</pane>
		<pane Orientation="horizontal" HorizAlign="Right">
			<edittext id="statusText" ReadOnly="true"/>
			<pushbutton id="runBtn" Name=" Run " Enabled="false" OnPressed="onRun()"/>
			<pushbutton Name=" Close " Enabled = "true" OnPressed="onClose()"/>
		</pane>
	</dialog>
</root>';
# parse XML text for the dialog into memory; 
# return an error code (number < 0 ) if there are syntax errors
err = dlgdoc.Parse(xml$);
if (err < 0) 
	{
	PopupError(err); 	# 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
dlgnode = dlgdoc.GetElementByID("mudlg");
if (dlgnode == 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.
dlgwin.SetXMLNode(dlgnode);
dlgwin.CreateModeless();
## get handles for dialog controls
ptVecText = dlgwin.GetCtrlByID("ptVecText");
polyVecText = dlgwin.GetCtrlByID("polyVecText");
mapVecText = dlgwin.GetCtrlByID("mapVecText");
outProjFileText = dlgwin.GetCtrlByID("outProjFileText");
statusText = dlgwin.GetCtrlByID("statusText");
runBtn = dlgwin.GetCtrlByID("runBtn");
dlgwin.Open();
WaitForExit();