RASTPROF.sml

  Download

More scripts: Display Toolbar

Syntax Highlighing:

comments, key words, predefined symbols, class members & methods, functions & classes
            
# RASTPROF.SML
# View ToolScript
# This sample toolscript allows the user to draw a profile line in the View using a 2-point line tool.
# It accumulates the raster values from the active raster along the line and then draws a
# graph showing the profile of the raster along the line.  The profile can be constructed using
# raw raster values or values averaged over a 3 x 3, 5 x 5, or 7 x 7 focal area.
#
# The active layer must be a continuous-value raster; composite rasters cannot be used.
# Requires TNT version 2012:78 or later.
# Version 26 August 2013
#		Changed the function to create the line tool to ViewCreateLineGadget()
#		to be consistent with internal class changes.
	# The following symbols are predefined
	#    class GRE_VIEW View            {use to access the view the tool script is attached to}
	#    class GRE_GROUP Group          {use to access the group being viewed if the script is run from a group view}
	#    class GRE_LAYOUT Layout        {use to access the layout being viewed if the script is run from a layout view}
	#    numeric ToolIsActive           Will be 0 if tool is inactive or 1 if tool is active
	#
	# The following values are also predefined and are valid when the various On...()
	# functions are called which deal with pointer and keyboard events.
	#    numeric PointerX            Pointer X coordinate within view in pixels
	#    numeric PointerY            Pointer Y coordinate within view in pixels
	#    numeric ShiftPressed        1 if <shift> key being pressed or 0 if not
	#    numeric CtrlPressed         1 if <ctrl> key being pressed or 0 if not
	#    numeric LeftButtonPressed   1 if left pointer button pressed or 0 if not
	#    numeric RightButtonPressed  1 if right pointer button pressed or 0 if not
	#    numeric MiddleButtonPressed 1 if middle pointer button pressed or 0 if not
	#
### GLOBAL VARIABLES
class GRE_GROUP activeGroup;				# the active group in the View
class GRE_LAYER rasterLayer;		# active layer containing the target raster
class RVC_RASTER targetRaster;	# target raster object
class GUI_DLG dlgwin;
class GUI_CANVAS canvas;			# drawing canvas in GUI Dialog window
class GC gc;							# graphics context for the canvas
class GUI_GADGET_SEGMENT profileLine;			# tool for drawing 2-point line in View
class STRING rasterName$;
numeric doGraph, hasNull;
class POINT2D startpoint;
class POINT2D endpoint;
array numeric value[1000000];
array numeric draw[1000000];
array numeric graphx[3], graphy[3];		# arrays for x & y coordinates of graph axes
numeric min, max, count, nullVal;
numeric setDefaultWhenClose = false;
# Checks layer to see if it is valid.
func checkLayer()
	{
	local numeric valid = false;
	# Get name of active layer if it is usable.  If not output an error message.
	if (activeGroup.ActiveLayer.Type == "") {
		rasterName$ = "Group has no layers!";
		}
	else {
		if (activeGroup.ActiveLayer.Type == "Raster") {
			rasterLayer = activeGroup.ActiveLayer;
			DispGetRasterFromLayer(targetRaster, rasterLayer);
			# Check for null values
			if (HasNull(targetRaster)) {
				hasNull = 1;
				nullVal = NullValue(targetRaster);
				}
			else
				hasNull = 0;
			if (targetRaster.$Info.Type != "binary" and
				targetRaster.$Info.Type != "4-bit unsigned" and
				targetRaster.$Info.Type != "8-bit signed" and
				targetRaster.$Info.Type != "8-bit unsigned" and
				targetRaster.$Info.Type != "16-bit signed" and
				targetRaster.$Info.Type != "16-bit unsigned" and
				targetRaster.$Info.Type != "32-bit signed" and
				targetRaster.$Info.Type != "32-bit unsigned" and
				targetRaster.$Info.Type != "32-bit floating-point" and
				targetRaster.$Info.Type != "64-bit signed" and
				targetRaster.$Info.Type != "64-bit unsigned" and
				targetRaster.$Info.Type != "64-bit floating-point") {
				rasterName$ = "Type not supported!";
				}
			else {
				rasterName$ = rasterLayer.Name;
				valid = true;
				}
			}
		else
			rasterName$ = "Not a raster!";
		}
	return valid;
	}
# Callback for drawing area expose.  Draws text and graph.
proc cbRedraw()
	{
	local class POLYLINE graphLine;	# polyline used to draw profile line(s)
	local class POINT2D graphPt;		# point for the current profile line vertex
	gc = canvas.CreateGC();
	# Clear the drawing area and redraw text.
	gc.SetColorRGB(255, 255, 255, 255);
	gc.FillRect(0, 0,  canvas.GetWidth(), canvas.GetHeight() );
	gc.SetColorName("black");
	gc.DrawTextSetFont("ARIALBD.TTF");
	gc.DrawTextSetHeight(12);
	gc.DrawTextSimple(sprintf("Raster: %s", rasterName$), 0, 10);
	if (doGraph == 0) return;
	local string min$, max$;
	local numeric size;
	### Draw graph axes
	min$ = sprintf("%d", min);
	max$ = sprintf("%d", max);
	size = strlen(max$);
	# set coordinates of upper end of y-axis
	graphx[1] = size * 6.5 + 5;
	graphy[1] = 20;
	# origin of x and y axes
	graphx[2] = graphx[1];
	graphy[2] = canvas.GetHeight() - 15;
	# right end of x-axis
	graphx[3] = canvas.GetWidth() - 5;
	graphy[3] = graphy[2];
	# draw the axes
	gc.DrawPolyLine(graphx, graphy, 3);
	gc.DrawTextSetFont("stork");
	gc.DrawTextSetHeight(10);
	gc.DrawTextSimple(max$, 0, graphy[1]+10);
	gc.DrawTextSimple(min$, (size - strlen(min$)) * 6.5, graphy[2]);
	gc.DrawTextSimple(sprintf("%d, %d", startpoint.x, startpoint.y), graphx[1], canvas.GetHeight() - 3);
	local string str$ = sprintf("%d, %d", endpoint.x, endpoint.y);
	gc.DrawTextSimple(str$, graphx[3] - strlen(str$) * 6.5, canvas.GetHeight() - 3);
	gc.SetColorName("magenta");	# color for drawing profile line
	local numeric xscale, yscale, prevnull;
	xscale = (graphx[3] - graphx[2] + 1) / (count - 1);
	yscale = (graphy[2] - graphy[1]) / (max - min);
	### Draw profile
	# add first profile point to the initial polyline
	if (value[1] < 0 || value[1] >= 0)	# if current value is not null
		{
		if (max - min == 0)		# all values are the same; set point at left end of horizontal profile line
			{
			graphPt.x = graphx[2];		graphPt.y = graphy[1] + (graphy[2]-graphy[1])*.5;
			}
		else							# set point at left end of profile line (on y-axis)
			{
			graphPt.x = graphx[2];	graphPt.y = graphy[2] - (value[1] - min) * yscale;
			}
		graphLine.AppendVertex(graphPt);		# add point to profile polyline
		}
	# loop through remaining items in the profile value array to draw profile line, leaving
	# gaps across areas of null cells (if any)
	local numeric i;
	for i = 2 to count
		{
		if (value[i] < 0 || value[i] >= 0)	# current value is not null
			{
			if (!(value[i-1] < 0 || value[i-1] >= 0))		# previous value was null; start new profile line segment
				{
				if (max - min == 0)		# all values are the same; set point at left edge of new segment
					{
					graphPt.x = graphx[2] + (i - 1) * xscale;
					graphPt.y = graphy[1] + (graphy[2] - graphy[1]) * .5;
					}
				else			# set point at left end of new segment of profile line
					{
					graphPt.x = graphx[2] + (i - 1) * xscale;
					graphPt.y = graphy[2] - (value[i] - min) * yscale;
					}
				graphLine.AppendVertex(graphPt);		# add point to profile polyline
				}
			else				# previous value was not null, continue current profile
				{
				if (max - min == 0)	# all values are the same; set point for horizontal profile
					{
					graphPt.x = graphx[2] + (i - 1) * xscale;
					graphPt.y = graphy[1] + (graphy[2] - graphy[1]) * .5;
					}
				else						# compute current point position on profile line
					{
					graphPt.x = graphx[2] + (i - 1) * xscale;
					graphPt.y = graphy[2] - (value[i] - min) * yscale;
					}
				graphLine.AppendVertex(graphPt);	# add point to profile polyline
				}
			}
		else			# current value is null
			{
			if (value[i-1] < 0 || value[i-1] >= 0)		# previous value is not null;
				{
				gc.DrawPolyLine2(graphLine);		# draw polyline for current profile segment to edge of this null area
				graphLine.Clear();					# clear polyline to start new profile segement at next non-null value
				}
			}
		}
	gc.DrawPolyLine2(graphLine);
	canvas.Refresh();
	graphLine.Clear();
	}
# Callback for when the active layer changes.
proc cbLayer()
	{
	checkLayer();
	cbRedraw();
	}
# Callback for when the active group changes.
proc cbGroup()
	{
	activeGroup = Layout.ActiveGroup;
	WidgetAddCallback(activeGroup.LayerSelectedCallback, cbLayer);
	cbLayer();
	}
# Callback for when user clicks the right mouse button on the line segment tool
proc cbToolApply()
	{
	if (checkLayer() )
		{
		local numeric avgSize = StrToNum(dlgwin.GetCtrlValueStr("avgSize") );
		# get coordinate transformation from screen to layer coordinates
		# (raster column and line coordinates)
		local class TRANS2D_MAPGEN trans;
		trans = View.GetTransLayerToScreen(rasterLayer, 1);
		startpoint = profileLine.start;
		endpoint = profileLine.end;
		# convert start and end point to view coordinates
		startpoint = trans.ConvertPoint2DFwd(startpoint);
		endpoint = trans.ConvertPoint2DFwd(endpoint);
		# Get x and y step values for sampling raster cell values along profile line
		local numeric ystep, xstep;
		local class POINT2D cursor;
		cursor = startpoint;
		count = 0;
		ystep = endpoint.y - startpoint.y;
		xstep = endpoint.x - startpoint.x;
		if (xstep == 0 && ystep == 0)			# profile start and end at same position
			return;
		# use the x/y extents of the profile line to compute step distance along line
		if (abs(xstep) > abs(ystep)) {
			ystep = ystep / abs(xstep);
			xstep = xstep / abs(xstep);
			if (ystep == 0) {
				count = abs(endpoint.x - startpoint.x);
				}
			else
				count = abs((endpoint.y - startpoint.y) / ystep);
			}
		else {
			xstep = xstep / abs(ystep);
			ystep = ystep / abs(ystep);
			if (xstep == 0) {
				count = abs(endpoint.y - startpoint.y);
				}
			else
				count = abs((endpoint.x - startpoint.x) / xstep);
			}
		count = count + 1;
		doGraph = 1;
		max = -1000000;
		min = 1000000;
		# Loop on line to generate point list.
		local numeric i;
		for i = 1 to count {
			if (avgSize == 1) then		# no averaging for raster values
				value[i] = targetRaster[round(cursor.y), round(cursor.x)];
			else		# use FocalMean function to get mean value for averaging area
				value[i] = FocalMean(targetRaster, avgSize, avgSize, round(cursor.y), round(cursor.x))
			# If the raster value was retrieved from a null cell or a coordinate
			# outside the raster extents, value[i] will be 'not a number'.
			# Therefore testing if it is < 0 or >= 0 determines whether it is
			# a valid value
			if (value[i] < 0 || value[i] >= 0)
				{
				if (value[i] > max)
					max = value[i];
				if (value[i] < min)
					min = value[i];
				}
			cursor.x = cursor.x + xstep;
			cursor.y = cursor.y + ystep;
			}
		if (max == -1000000 && min == 1000000)
			{
			max = 0;
			min = 0;
			}
		cbRedraw();
		}
	}		# end of cbToolApply
# Called when the close button is pressed.  Closes the dialogs.
proc cbClose()
	{
	profileLine.Managed = 0;
	dlgwin.Close(0);
	if (setDefaultWhenClose) {
		setDefaultWhenClose = false;
		View.SetDefaultTool();
		}
	}
	# The following script functions will be called (if used in the script) when
	# the appropriate action or event occurs as described in the comments before each.
	# To use a function, uncomment the lines containing the 'func' definition
	# and ending brace '}' by removing the leftmost '#' on the line and add the
	# function code between the two lines.
# Called the first time the tool is activated.
# If the tool implements a dialog it should be created (but not displayed) here.
proc OnInitialize ()
	{
	if (Layout)
		{
		WidgetAddCallback(Layout.GroupSelectedCallback, cbGroup);
		activegroup = Layout.ActiveGroup;
		}
	else
		activeGroup = Group;
	WidgetAddCallback(activegroup.LayerSelectedCallback, cbLayer);
	# Add the 2-point line tool (segment)
	profileLine = ViewCreateLineGadget(View);
	ToolAddCallback(profileLine.ApplyCallback, cbToolApply);
	# string with dialog specification
	xml$ = '<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE root SYSTEM "smlforms.dtd">
	<root>
		<dialog id="rastprof" Title="Raster Profile" Buttons="">
			<groupbox ExtraBorder="2" Orientation="horizontal">
				<canvas HorizResize="Expand" id="canvas" Height="175" Width="300"/>
			</groupbox>
			<pane Orientation = "horizontal">
				<label>Height/width of averaging area (cells) </label>
				<combobox id = "avgSize" Default = "3" OnSelection = "cbToolApply();">
					<item Name = "1" Value = "1"/>
					<item Name = "3" Value = "3"/>
					<item Name = "5" Value = "5"/>
					<item Name = "7" Value = "7"/>
				</combobox>
			</pane>
		</dialog>
	</root>';
	### parse XML string; returns an error code (number < 0 ) if there are syntax errors
	local class XMLDOC doc;
	err = doc.Parse(xml$);
	if (err < 0)
		{
		PopupError(err); # Popup an error dialog. "Details" button shows syntax errors.
		Exit();
		}
	### declare class instance for the dialog element in the XML structure
	### and get the dialog handle from the XML structure.
	### Pop up an error dialog and exit if the dialog ID can't be found in the XML.
	class XMLNODE dlgnode;
	dlgnode = doc.GetElementByID("rastprof");
	if (dlgnode == 0)
		{
		PopupMessage("Could not find dialog node in XML document");
		Exit();
		}
	### set the XML structure in memory as the source for the dialog.
	dlgwin.SetXMLNode(dlgnode);
	err = dlgwin.CreateModeless();
	if (err < 0)
		{
		PopupError(err); # Popup an error dialog. "Details" button shows syntax errors.
		Exit();
		}
	# get handle for drawing canvas in dialog window
	canvas = dlgwin.GetCtrlByID("canvas");
	} # end of OnInitialize
# Called when tool is to be destroyed, will not be called if tool was never activated.
# If the tool implements a dialog it should be destroyed here.
# proc OnDestroy () {
# } # end of OnDestroy
# Called when tool is activated.
# If the tool implements a dialog it should be "managed" (displayed) here.
proc OnActivate ()
	{
	local numeric err;
	checkLayer();
	profileLine.Managed = 1;
	profileLine.HasPosition = 0;
	err = dlgwin.Open();
	cbRedraw();
	setDefaultWhenClose = true;
	}  # end of OnActivate
# Called when tool is deactivated (usually when switching to another tool).
# If the tool implements a dialog it should be "unmanaged" (hidden) here.
proc OnDeactivate ()
	{
	setDefaultWhenClose = false;
	cbClose();
	}  # end of OnDeactivate
# Called when tool is to be 'suspended' during a redraw operation.
# proc OnSuspend () {
# }  # end of OnSuspend
# Called when tool is to be 'resumed' after a redraw operation.
# If the tool displays any graphics they should be updated by this function.
# proc OnResume () {
# }  # end of OnResume
# Called when user presses 'left' pointer/mouse button.
# proc OnLeftButtonPress () {
# }  # end of OnLeftButtonPress
# Called when user presses 'right' pointer/mouse button.
#proc OnRightButtonPress () {
#}  # end of OnRightButtonPress
# Called when user presses 'middle' pointer/mouse button.
# proc OnMiddleButtonPress () {
# }  # end of OnMiddleButtonPress
# Called when user releases 'left' pointer/mouse button.
# proc OnLeftButtonRelease () {
# }  # end of OnLeftButtonRelease
# Called when user releases 'right' pointer/mouse button.
# proc OnRightButtonRelease () {
# }  # end of OnRightButtonRelease
# Called when user releases 'middle' pointer/mouse button.
# proc OnMiddleButtonRelease () {
# }  # end of OnMiddleButtonRelease
# Called when user moves cursor if no button being pressed
# proc OnPointerMoveNoButton () {
# }  # end of OnPointerMoveNoButton
# Called when user moves cursor while holding down button
# proc OnPointerMoveWithButton () {
# }  # end of OnPointerMoveWithButton
# Called when cursor enters window associated with view.
# proc OnEnterWindow () {
# }  # end of OnEnterWindow
# Called when cursor leaves window associated with view.
# proc OnLeaveWindow () {
# }  # end of OnLeaveWindow
# Called when user presses 'key' on keyboard.
# proc OnKeyPress (key) {
# }  # end of OnKeyPress