LineProfile.sml

  Download

More scripts: Display Toolbar

Syntax Highlighing:

comments, key words, predefined symbols, class members & methods, functions & classes
            
#
# This script is meant to be run as a Toolscript
#
# Current Assumptions:
# In the group there are two layers:
# Layer 1: DEM - not needed
# Layer 2: Overlaying vector lines (must be last layer)
#
# Purpose:
# The script creates a window displaying a plotted profile of the
# selected lines nearest line, using the vector DB values for elevation.
#
# Usage:
# Left click: toggle select/deselect line
# Right click: Accept current line and draw
#
# GUI global definitions
class GUI_DLG dlgwin;
class GUI_CANVAS canvas;
class GC gc;
class GUI_CTRL_TOGGLEBUTTON gridToggle;
class GUI_CTRL_EDIT_NUMBER gridIntervalX;
class GUI_CTRL_EDIT_NUMBER gridIntervalY;
class GUI_CTRL_TOGGLEBUTTON demToggle;
class GUI_CTRL_EDIT_NUMBER graphMinSetting;
class GUI_CTRL_EDIT_NUMBER graphMaxSetting;
class GUI_CTRL_TOGGLEBUTTON fillToggle;
#class GUI_CTRL_EDIT_NUMBER elemDisplay;
#class GUI_CTRL_EDIT_NUMBER lineDisplay;
#class GUI_CTRL_EDIT_NUMBER vertexDisplay;
class GUI_CTRL_EDIT_NUMBER mouseXDisplay;
class GUI_CTRL_EDIT_NUMBER mouseYDisplay;
class GUI_CTRL_EDIT_NUMBER slopeDisplay;
class GUI_CTRL_EDIT_NUMBER diameterDisplay;
class GUI_CTRL_EDIT_STRING materialDisplay;
# Group/layer/object global definitions
class GRE_GROUP activegroup;
class TRANSPARM mapTrans;
class GRE_LAYER_VECTOR vectorLayer;
class GRE_LAYER_RASTER rasterLayer;
class VECTOR lineVector;
class GEOREF vecGeoref;
class RASTER dem;
string vectorName$;
class DBTABLEINFO nodeTable;
class DBTABLEINFO lineTable;
numeric dbIsInit=0;
# Graphical display global definitions
class POLYLINE pipeBottomSave, pipeTopSave, pipeFaceSave; # used for highlighting
class STRINGLIST colors;
numeric drawable=0, demSet=0;
numeric graphMinZ, graphMaxZ, dataMinZ, dataMaxZ;
numeric setDefaultWhenClose = false;
numeric leftGraphOffset, bottomGraphOffset=20, rightGraphOffset=15, topGraphOffset=50; #5,15
numeric currentlyActive = -1;
numeric needsRedraw = 0;
# Utility global definitions
class POLYLINE demLine;
class STRINGLIST elemList;
class STRINGLIST orderedElemList;
class STRINGLIST reversedList;
array numeric endNodes[2];
numeric MAX_NUMBER = 9999999;
# <debug>
func writePolyline(class FILE f, class POLYLINE polyline)
{
	local class POINT2D p2d;
	local numeric i;
	for (i=0; i<polyline.GetNumPoints(); i++)
	{
		p2d = polyline.GetVertex(i);
		fwritestring(f, sprintf("(%.3f, %.3f)\n", p2d.x, p2d.y));
	}
	fwritestring(f, "\n");
}
# </debug>
func assignColors()
{
	colors.AddToEnd("red");
	colors.AddToEnd("orange");
	colors.AddToEnd("yellow");
	colors.AddToEnd("green");
	colors.AddToEnd("blue");
	colors.AddToEnd("purple");
	colors.AddToEnd("violet");
}
func class COLOR getColor(numeric index)
{
	local class COLOR c;
	c.Name = colors.GetString(index%colors.GetNumItems()-1);
	return c;
}
# Determine if the dem will be used or not
func doUseDEM()
{
	return demToggle.GetValue();
}
# Get the line table by name - return 1 if successful, 0 if table is null
func initLineTable(string tablename)
{
	local class DATABASE lineDB = OpenVectorLineDatabase(lineVector);
	if (lineDB==0) return 0;
	lineTable = DatabaseGetTableInfo(lineDB, tablename);
	if (lineTable==0) return 0;
	return 1;
}
# Get the node table by name - return 1 if successful, 0 if table is null
func initNodeTable(string tablename)
{
	local class DATABASE nodeDB = OpenVectorNodeDatabase(lineVector);
	if (nodeDB==0) return 0;
	nodeTable = DatabaseGetTableInfo(nodeDB, tablename);
	if (nodeTable==0) return 0;
	return 1;
}
# Checks layer to see if it is valid.
func checkLayer()
{
	if (doUseDEM())
	{
		# Raster is assumed to be first layer
		rasterLayer = activegroup.FirstLayer;
		if (rasterLayer.Type!="Raster")
		{
			demSet = 0;
			PopupMessage("The first layer is not a raster, the DEM was not assigned properly and the script may not function properly.");
		}
		DispGetRasterFromLayer(dem, rasterLayer);
		demSet = 1;
	}
	# Vector is assumed to be last layer
	vectorLayer = activegroup.LastLayer;
	DispGetVectorFromLayer(lineVector, vectorLayer);
	vecGeoref = GetLastUsedGeorefObject(lineVector);
	mapTrans.InputProjection = vectorLayer.Projection;
	mapTrans.OutputProjection = rasterLayer.Projection;
	# initialize the database tables
	initLineTable("SEW_INFO");
	initNodeTable("MH_GIS");
	return 1;
}
# Callback for when the active layer changes.
proc cbLayer()
{
	checkLayer();
}
# Callback for when the active group changes.
proc cbGroup()
{
	activegroup = Layout.ActiveGroup;
	WidgetAddCallback(activegroup.LayerSelectedCallback, cbLayer);
	cbLayer();
}
# return the max of two numbers
func max(numeric n1, numeric n2)
{
	if (n1 > n2) return n1;
	return n2;
}
# Get the element from our list
func getElement(class STRINGLIST list, numeric index)
{
	return StrToNum(list.GetString(index));
}
# Get the index of the element in our list, return index if found, -1 otherwise
func indexOf(class STRINGLIST list, numeric value)
{
	local numeric i;
	for (i=0; i<list.GetNumItems(); i++)
	{
		if (getElement(list, i)==value) 
		{
			return i;
		}
	}
	return -1;
}
# Determine if the two lines are consecutive (endpt1 == startpt2)
#func isConsecutive(class POLYLINE line, numeric vertexNum)
#{
#	if (indexOf(endPoints, vertexNum)>-1)
#	{
#		local class POINT2D point1 = line.GetVertex(vertexNum);
#		local class POINT2D point2 = line.GetVertex(vertexNum+1);
#
#		if (point1!=point2)
#		{
#			return 0;
#		}
#	}
#	return 1;
#}
# Compute the distance between two points
func computeDistance(class POINT2D p1, class POINT2D p2)
{
	return sqrt((p2.x-p1.x)^2 + (p2.y-p1.y)^2);
}
# Determine if the given lin, col is within the raster extents
func isPointInRaster(numeric lin, numeric col)
{
	if (lin<1 || col<1 || lin>NumLins(dem) || col>NumCols(dem)) return 0;
	return 1;
}
# Get the value of the first record attached to the node "nodeNum" as a string
func string readNodeTableRecordStr(numeric nodeNum, string field)
{
	local array numeric records[1];
	TableReadAttachment(nodeTable, nodeNum, records, "node");
	local string retVal = TableReadFieldStr(nodeTable, field, records[1]);
	return retVal;
}
# Get the value of the first record attached to the line "lineNum" as a string
func string readLineTableRecordStr(numeric lineNum, string field)
{
	local array numeric records[1];
	TableReadAttachment(lineTable, lineNum, records, "line");
	local string retVal = TableReadFieldStr(lineTable, field, records[1]);
	return retVal;
}
# Get the value of the first record attached to the line "lineNum"
func readLineTableRecord(numeric lineNum, string field)
{
	local array numeric records[1];
	TableReadAttachment(lineTable, lineNum, records, "line");
	local numeric retVal = TableReadFieldNum(lineTable, field, records[1]);
	if (IsNull(retVal)) return 0;
	return retVal;
}
# Get the value of the first record attached to the line "nodeNum"
func readNodeTableRecord(numeric nodeNum, string field)
{
	local array numeric records[1];
	TableReadAttachment(nodeTable, nodeNum, records, "node");
	local numeric retVal = TableReadFieldNum(nodeTable, field, records[1]);
	return retVal;
}
# Get the z value
func computeElevationFromDEM(class POINT2D p)
{
	p = mapTrans.ConvertPoint2DFwd(p);	# convert from vector map to raster map coords
	local class POINT2D obj = MapToObject(GetLastUsedGeorefObject(dem), p.x, p.y, dem);
	obj.x = obj.x + .5; # center of cell
	obj.y = obj.y + .5; # center of cell
	local numeric demValue;
	if(isPointInRaster(obj.y, obj.x)) demValue = dem[obj.y, obj.x];	# y for line, x for column
	else demValue = null;
	return demValue;
}
# Find the closest line element using the polyline and the two nodes (vertices)
func findClosestLineElement(class POLYLINE origLine, numeric vertex1, numeric vertex2)
{
	# get the line given the two end points - straight line assumed
	local class POINT2D point1 = origLine.GetVertex(vertex1);
	local class POINT2D point2 = origLine.GetVertex(vertex2);
	local class POINT2D midPt = point1;
	midPt = midPt + point2;
	midPt.x = midPt.x / 2;
	midPt.y = midPt.y / 2;
	local numeric elemNum = FindClosestLine(lineVector, midPt.x, midPt.y);
	return elemNum;
}
# Interpolate between two points adding extra vertices
func class POLYLINE constructSmoothSurface(class POLYLINE p, numeric linscale, numeric colscale, numeric samplingRate)
{
	local class POLYLINE tmpLine, retLine;
	local class POINT2D point1, point2, tmpPt;
	local numeric numIntervals = 3;
	local numeric i;
	for (i=1; i<p.GetNumPoints(); i++)
	{
		point1 = p.GetVertex(i-1);
		point2 = p.GetVertex(i);
		local class POINT2D intervalPt = point2 - point1;
		intervalPt.x = intervalPt.x / (numIntervals+1);
		intervalPt.y = intervalPt.y / (numIntervals+1);
		if (intervalPt.x != 0 && intervalPt.y != 0)
		{
			tmpPt = point1;
			local numeric j;
			for (j=0; j<=numIntervals+1; j++)
			{
				tmpLine.AppendVertex(tmpPt);
				tmpPt = tmpPt + intervalPt;
			}
		}
	}
	local numeric distance=0;
	local numeric elevation = computeElevationFromDEM(tmpLine.GetVertex(0));
	tmpPt.x = distance;
	tmpPt.y = elevation;
	retLine.AppendVertex(tmpPt);
	for (i=1; i<tmpLine.GetNumPoints(); i++)
	{
		distance += computeDistance(tmpLine.GetVertex(i-1), tmpLine.GetVertex(i));
		elevation = computeElevationFromDEM(tmpLine.GetVertex(i));
		tmpPt.x = distance;
		tmpPt.y = elevation;
		retLine.AppendVertex(tmpPt);
	}
	return retLine;
}
# Construct the DEM surface elevation line, x dimension is line distance y is elevation
func class POLYLINE constructDemSurface(class POLYLINE demLine, class POLYLINE interpolated)
{
	local class POLYLINE retLine;
	if (demLine.GetNumPoints()<1) return retLine;
	local class POINT2D tmp;
	local numeric i;
	local numeric distance=0;
	local numeric elevation = computeElevationFromDEM(demLine.GetVertex(0));
	tmp.x = distance;
	tmp.y = elevation;
	retLine.AppendVertex(tmp);
	# get the elevation values from the raster
	for (i=1; i<demLine.GetNumPoints(); i++)
	{
		distance += computeDistance(demLine.GetVertex(i-1), demLine.GetVertex(i));
		elevation = computeElevationFromDEM(demLine.GetVertex(i));
		tmp.x = distance;
		tmp.y = elevation;
		retLine.AppendVertex(tmp);
	}
	return retLine;
}
# returns 1 if elemNum is reversed, 0 otherwise
func isLineReversed(numeric elemNum)
{
	return StrToNum(reversedList.GetString(indexOf(elemList, elemNum)));
}
# Construct the surface elevation line, x dimension is line distance y is elevation
func class POLYLINE constructSurface(class POLYLINE origLine)
{
	# DS == end node, US == start node
	local string pipeField = "CAN_I_US";
	local string surfField = "CAN_D_US";
	local class POLYLINE newLine;
	if (origLine.GetNumPoints()<1) return newLine;
	local class POINT2D tmp;
	local numeric i;
	local numeric distance=0;
	local numeric elemNum = findClosestLineElement(origLine, 0, 1);
	# reversed line 0 - can happen if first line is toggled off (not used as we flip bits in that case)
	if (isLineReversed(elemNum))
	{
		pipeField = "CAN_I_DS";
		surfField = "CAN_D_DS";
	}
	# Get elevation from start node
	local numeric elevation = readLineTableRecord(elemNum, pipeField) + readLineTableRecord(elemNum, surfField);
	if (!IsNull(elevation))
	{
		tmp.x = distance;
		tmp.y = elevation;
		newLine.AppendVertex(tmp);
	}
	# get the elevation values from the database table
	for (i=1; i<origLine.GetNumPoints(); i++)
	{
		local class POINT2D point1, point2;
		point1 = origLine.GetVertex(i-1);
		point2 = origLine.GetVertex(i);
		local numeric dx = computeDistance(point1, point2);
		distance += dx;
		# DS == end node, US == start node
		pipeField = "CAN_I_DS";
		surfField = "CAN_D_DS";
		local numeric k=0;
		# dx ==0 means we found a start node (thus US)
		if (dx==0)
		{
			k = 1;
			pipeField = "CAN_I_US";
			surfField = "CAN_D_US";
		}
		local numeric elemNum = findClosestLineElement(origLine, i-1+k, i+k);
		# check if the line was reversed - need to change field used
		if (isLineReversed(elemNum))
		{
			if (pipeField == "CAN_I_US") pipeField = "CAN_I_DS";
			else if (pipeField == "CAN_I_DS") pipeField = "CAN_I_US";
			if (surfField == "CAN_D_US") surfField = "CAN_D_DS";
			else if (surfField == "CAN_D_DS") surfField = "CAN_D_US";
		}
		# Get elevation from end node
		elevation = readLineTableRecord(elemNum, pipeField) + readLineTableRecord(elemNum, surfField);
		if (!IsNull(elevation))
		{
			tmp.x = distance;
			tmp.y = elevation;
			newLine.AppendVertex(tmp);
		}
	}
	return newLine;
}
# Construct the pipe face by creating a rectangle from four vertices of 'bottom' and 'top'
func class POLYLINE constructPipeFace(class POLYLINE bottom, class POLYLINE top)
{
	local class POLYLINE ret;
	if (bottom.GetNumPoints() != top.GetNumPoints())
	{
		numeric fillPipes = 0;
		return ret;
	}
	local numeric i;
	for (i=0; i<bottom.GetNumPoints(); i=i+2)
	{
		ret.AppendVertex(bottom.GetVertex(i));		# LL
		ret.AppendVertex(bottom.GetVertex(i+1));	# LR
		ret.AppendVertex(top.GetVertex(i+1));		# UR
		ret.AppendVertex(top.GetVertex(i));			# UL
		ret.AppendVertex(bottom.GetVertex(i));		# LL
	}
	return ret;
}
# Construct the pipe top - top.x = bottom.x && top.x = bottom.x + pipeHeight
func class POLYLINE constructPipeTop(class POLYLINE pipeBottom, class POLYLINE origLine)
{
	local class POLYLINE newLine;
	local class POINT2D tmp;
	# get the pipe top by calculating as heigth offset from bottom pipe line
	local numeric i;
	for (i=0; i<origLine.GetNumPoints(); i++)
	{
		local numeric vertex1, vertex2;
		if (i==0) vertex1 = i;
		else vertex1 = i-1;
		vertex2 = i;
		local class POINT2D p1 = origLine.GetVertex(vertex1);
		local class POINT2D p2 = origLine.GetVertex(vertex2);
		if (p1==p2)
		{
			vertex1++;
			vertex2++;
			if (i==0) vertex1 = i;
		}
		local numeric elemNum = findClosestLineElement(origLine, vertex1, vertex2);
		# Get elevation from end node
		local numeric elevation = readLineTableRecord(elemNum, "CAN_DIAM")/1000 + pipeBottom.GetVertex(i).y;
		if (!IsNull(elevation))
		{
			tmp.x = pipeBottom.GetVertex(i).x;
			tmp.y = elevation;
			newLine.AppendVertex(tmp);
		}
	}
	return newLine;
}
# Construct the graph line, x dimension is line distance y is elevation
func class STRINGLIST constructManholeNames(class POLYLINE origLine)
{
	local class STRINGLIST manholeNames;
	local class POINT2D prevPoint;
	# Draw all the manhole names at the manhole tops
	local numeric i;
	for (i=0; i<origLine.GetNumPoints(); i++)
	{
		# get the label
		local class POINT2D point1 = origLine.GetVertex(i);
		local string manholeLabel = "";
		if (point1!=prevPoint)
		{
			local numeric elemNum = FindClosestNode(lineVector, point1.x, point1.y);
			manholeLabel = readNodeTableRecordStr(elemNum, "MH_ID");
		}
		manholeNames.AddToEnd(manholeLabel);
		prevPoint = point1;
	}
	return manholeNames;
}
# Construct the graph line, x dimension is line distance y is elevation
func class POLYLINE constructManholeDepth(class POLYLINE pipeBottom, class POLYLINE origLine)
{
	local class POLYLINE newLine;
	local class POINT2D tmp;
	local numeric i;
	for (i=0; i<origLine.GetNumPoints(); i++)
	{
		local class POINT2D point1 = origLine.GetVertex(i);
		local numeric elemNum = FindClosestNode(lineVector, point1.x, point1.y);
		# Get elevation from end node
		local numeric elevation = readNodeTableRecord(elemNum, "MH_INVERT");
		tmp.x = pipeBottom.GetVertex(i).x;
		tmp.y = elevation;
		newLine.AppendVertex(tmp);
	}
	return newLine;
}
# Construct the graph line, x dimension is line distance y is elevation
func class POLYLINE constructPipeBottom(class POLYLINE origLine)
{
	local string field = "CAN_I_US";
	local class POLYLINE newLine;
	if (origLine.GetNumPoints()<1) return newLine;
	local class POINT2D tmp;
	local numeric i;
	local numeric distance=0;
	local numeric elemNum = findClosestLineElement(origLine, 0, 1);
	# check if the line 0 was reversed -- can happen if first line was toggled off (not used as we flip bits in that case)
	if (isLineReversed(elemNum))
	{
		field = "CAN_I_DS";
	}
	# Get elevation from start node
	local numeric elevation = readLineTableRecord(elemNum, field);
	if (!IsNull(elevation))
	{
		tmp.x = distance;
		tmp.y = elevation;
		newLine.AppendVertex(tmp);
	}
	for (i=1; i<origLine.GetNumPoints(); i++)
	{
		local class POINT2D point1, point2;
		point1 = origLine.GetVertex(i-1);
		point2 = origLine.GetVertex(i);
		local numeric dx = computeDistance(point1, point2);
		distance += dx;
		field = "CAN_I_DS";
		local numeric k=0;
		if (dx==0)
		{
			k = 1;
			field = "CAN_I_US";
		}
		local numeric elemNum = findClosestLineElement(origLine, i-1+k, i+k);
		# check if the line was reversed
		if (isLineReversed(elemNum))
		{
			if (field == "CAN_I_US") field = "CAN_I_DS";
			else if (field == "CAN_I_DS") field = "CAN_I_US";
		}
		# Get elevation from end node
		elevation = readLineTableRecord(elemNum, field);
		if (!IsNull(elevation))
		{
			tmp.x = distance;
			tmp.y = elevation;
			newLine.AppendVertex(tmp);
		}
	}
	return newLine;
}
# Determine if the value given is a null value
func isNull(numeric value)
{
#	return (value<=0);
	return (value < graphMinZ || value > graphMaxZ);
#	return (value == NullValue(dem));
}
# Get the width from the appropriate drawing device
func getHeight()
{
	return canvas.GetHeight();
}
# Get the width from the appropriate drawing device
func getWidth()
{
	return canvas.GetWidth();
}
# Get the extents of the graph
# (x1, y1) == UL corner
# (x2, y2) == LR corner
func class RECT getGraphExtents()
{
	local class RECT rect;
	rect.x1 = leftGraphOffset;
	rect.x2 = getWidth() - rightGraphOffset;
	rect.y1 = topGraphOffset;
	rect.y2 = getHeight() - bottomGraphOffset;
	return rect;
}
# Set the affine transformation for distance/elevation -> Graph Coords
proc setTrans(class POLYLINE graphLine)
{
	# copy the point
#	local class POINT2D retpoint;
#	retpoint = point;
#
#	# get graph extents
#	local numeric minx, maxx, miny, maxy;
#	minx = leftGraphOffset;
#	miny = topGraphOffset;
#	maxx = getWidth() - rightGraphOffset;
#	maxy = getHeight() - bottomGraphOffset;
#
#	# Get the drawing scale
#	local numeric xscale = 0, yscale = 0;
#	xscale = (maxx - minx) / abs(graphLine.GetVertex(graphLine.GetNumPoints()-1).x - graphLine.GetVertex(0).x);
#	if (graphMaxZ != graphMinZ) yscale = (maxy - miny) / (graphMaxZ - graphMinZ);
#
#	# apply scales and offsets
#	retpoint.x = point.x * xscale + leftGraphOffset;
#	if (yscale!=0) retpoint.y = getHeight() - ((point.y-graphMinZ) * yscale + bottomGraphOffset);
#	else retpoint.y = getHeight() - bottomGraphOffset;
	# redefinition here clears the trans each time we draw (which is desirable)
	class TRANSAFFINE trans;
	trans.ApplyScale(1,1);
	trans.ApplyOffset(0,0);
	# get graph extents
	local numeric minx, maxx, miny, maxy;
	minx = leftGraphOffset;
	miny = topGraphOffset;
	maxx = getWidth() - rightGraphOffset;
	maxy = getHeight() - bottomGraphOffset;
	# Get the drawing scale
	local numeric xscale = 0, yscale = 0;
	xscale = (maxx - minx) / abs(graphLine.GetVertex(graphLine.GetNumPoints()-1).x - graphLine.GetVertex(0).x);
	if (graphMaxZ != graphMinZ) yscale = (maxy - miny) / (graphMaxZ - graphMinZ);
	# apply scales and offsets
	trans.ApplyScale(xscale, -yscale);
	trans.ApplyOffset(leftGraphOffset, getHeight() + graphMinZ*yscale - bottomGraphOffset);
}
# Create the GC here using the appropriate drawing device (gc is global)
proc createGC()
{
	gc = canvas.CreateGC();
}
# draw the graph background fill
proc drawBackground(class GC gc, class COLOR bgcolor)
{
	if (drawable)
	{
		# draw the background rectangle
		gc.SetColorRGB(bgcolor.red, bgcolor.green, bgcolor.blue, 100);
		gc.FillRect(0, 0, getWidth(), getHeight());
	}
}
# draw the axes for the graph - dataMinZ == MAX_NUMBER and dataMaxZ == -MAX_NUMBER are displayed as null
proc drawGraphAxes(class GC gc, numeric distance, string xAxisLabel, string yAxisLabel, numeric drawTwoPointLines, class COLOR axiscolor, numeric fontHeight, numeric axisLabelOffset)
{
	if (drawable)
	{
		# set the axis colors
		gc.SetColorRGB(axiscolor.red, axiscolor.green, axiscolor.blue, 100);
		# set the min and max display variables
		local string min$ = sprintf("%0.2f", graphMinZ);
		local string max$ = sprintf("%0.2f", graphMaxZ);
		if (graphMaxZ==-MAX_NUMBER) max$ = "null";
		if (graphMinZ==MAX_NUMBER) min$ = "null";
		# Draw text for coordinate and elevation axis labels
		gc.DrawTextSetFont("ARIAL");
		gc.DrawTextSetHeightPixels(fontHeight);
		# Draw graph axes
		if (graphMaxZ == graphMinZ) max$ = "";
		local array numeric graphx[3], graphy[3];
		graphx[1] = leftGraphOffset;
		graphy[1] = topGraphOffset;
		graphx[2] = leftGraphOffset;
		graphy[2] = getHeight() - bottomGraphOffset;
		graphx[3] = getWidth() - rightGraphOffset;
		graphy[3] = graphy[2];
		gc.DrawPolyLine(graphx, graphy, 3);
		# draw y axis labels
		gc.TextStyle.Smoothing = 1;
		gc.DrawTextSimple(max$, 0, graphy[1]+fontHeight/2);
		gc.DrawTextSimple(min$, 0, graphy[2]+fontHeight/2);
		gc.DrawTextSimple(yAxisLabel, graphx[1]-axisLabelOffset, (graphy[3]-graphy[1])/2+gc.TextGetWidth(yAxisLabel)*1.5, 90);
		# draw x axis labels
		gc.DrawTextSimple("0", graphx[1] - gc.TextGetWidth("0")/2, getHeight()-3);
		local string str$ = sprintf("%0.2f", distance);
		gc.DrawTextSimple(str$, getWidth() - gc.TextGetWidth(str$), getHeight()-3);
		gc.DrawTextSimple(xAxisLabel, (graphx[3]-graphx[1])/2-gc.TextGetWidth(xAxisLabel)/4, graphy[3]+fontHeight+axisLabelOffset);
	}
}
# Translate a point on the graphline to image device coordinates for drawing
func class POINT2D transPointToGraph(class POINT2D point, class POLYLINE graphLine)
{
	local class POINT2D retpoint;
	if (trans!=0) retpoint = trans.ConvertPoint2DFwd(point);
	# copy the point
#	local class POINT2D retpoint;
#	retpoint = point;
#
#	# get graph extents
#	local numeric minx, maxx, miny, maxy;
#	minx = leftGraphOffset;
#	miny = topGraphOffset;
#	maxx = getWidth() - rightGraphOffset;
#	maxy = getHeight() - bottomGraphOffset;
#
#	# Get the drawing scale
#	local numeric xscale = 0, yscale = 0;
#	xscale = (maxx - minx) / abs(graphLine.GetVertex(graphLine.GetNumPoints()-1).x - graphLine.GetVertex(0).x);
#	if (graphMaxZ != graphMinZ) yscale = (maxy - miny) / (graphMaxZ - graphMinZ);
#
#	# apply scales and offsets
#	retpoint.x = point.x * xscale + leftGraphOffset;
#	if (yscale!=0) retpoint.y = getHeight() - ((point.y-graphMinZ) * yscale + bottomGraphOffset);
#	else retpoint.y = getHeight() - bottomGraphOffset;
	return retpoint;
}
# Plot the vertex and any connecting lines in the graphLine
# (note: this method is a bit more clever as it doesn't translate a point more than once
# but is not being used currently - it does make drawPolyline a bit less clear however).
proc plotLine(class GC gc, class POLYLINE graphLine, numeric vertexNum)
{
	local class POINT2D linePoint, graphPoint;
	linePoint = graphLine.GetVertex(vertexNum);
	graphPoint = transPointToGraph(linePoint, graphLine);
	if (0)#!isConsecutive(graphLine, vertexNum))
	{
		# if not consecutive, finish drawing and move to next
		gc.DrawTo(graphPoint.x, graphPoint.y);
		linePoint = graphLine.GetVertex(vertexNum+1);
		graphPoint = transPointToGraph(linePoint, graphLine);
		gc.MoveTo(graphPoint.x, graphPoint.y);
	}
	else if (isNull(linePoint.y))
	{
		# if null skip point and move to next
		linePoint = graphLine.GetVertex(vertexNum+1);
		graphPoint = transPointToGraph(linePoint, graphLine);
		gc.MoveTo(graphPoint.x, graphPoint.y);
	}
	else
	{
		gc.DrawTo(graphPoint.x, graphPoint.y);
	}
}
# draw a two point line from the polyline p
proc drawLineSegment(class POLYLINE p, numeric vertex1, numeric vertex2, class COLOR color)
{
	local class POINT2D point1, point2;
	point1 = p.GetVertex(vertex1);
	point2 = p.GetVertex(vertex2);
	if (isNull(point1.y)||isNull(point2.y))
	{
		# if null skip point and move to next
		point2 = transPointToGraph(point2, p);
		gc.MoveTo(point2.x, point2.y);
	}
	else
	{
		point1 = transPointToGraph(point1, p);
		point2 = transPointToGraph(point2, p);
		gc.SetColorRGB(color.red, color.green, color.blue, 100);
		gc.MoveTo(point1.x, point1.y);
		gc.DrawTo(point2.x, point2.y);
	}
}
# Draw the graph with the given polyline (not used currently - use with plotLine)
proc drawPolyline2(class GC gc, class POLYLINE graphLine, class COLOR lineColor)
{
	if (drawable && graphLine.GetNumPoints()>0)
	{
		# Draw the profile
		gc.SetColorRGB(lineColor.red, lineColor.green, lineColor.blue, 100);
		local class POINT2D linePoint = graphLine.GetVertex(0);
		# Plot point zero
		local class POINT2D graphPoint = transPointToGraph(linePoint, graphLine);
		gc.DrawPoint(graphPoint.x, graphPoint.y);
		# Plot the rest of the points - connecting them as lines
		local numeric i;
		for (i=0; i<graphLine.GetNumPoints(); i++)
		{
			plotLine(gc, graphLine, i);
		}
	}
}
# Draw the graph with the given polyline
proc drawPolyline1(class GC gc, class POLYLINE graphLine, class COLOR lineColor)
{
	if (drawable && graphLine.GetNumPoints()>0)
	{
		# Plot the polyline one segment at a time
		local numeric i;
		for (i=0; i<graphLine.GetNumPoints()-1; i++)
		{
			drawLineSegment(graphLine, i, i+1, lineColor);
		}
	}
}
# Draw the graph with the given polyline
proc drawPolyline(class GC gc, class POLYLINE graphLine, class COLOR lineColor)
{
	# Create a copy
	local class POLYLINE line = graphLine;
	line.ConvertForwardAffine(trans);
	# Set the line color
	gc.SetColorRGB(lineColor.red, lineColor.green, lineColor.blue, 100);
	# Draw it with gc method
	gc.DrawPolyLine2(line);
}
# Draw the graph with the given polyline as rectangles
# It is assumed that numPoints%5 = 0 (i.e. all rectangles are closed)
proc drawRectangles(class GC gc, class POLYLINE graphLine, class COLOR lineColor, class COLOR fillColor, numeric doFill)
{
	local class POLYLINE rectangle;
	if (drawable && graphLine.GetNumPoints()>=5)
	{
		local numeric i;
		for (i=0; i<graphLine.GetNumPoints(); i=i+5)
		{
			# Extract the rectangle and convert to graph coords
			graphLine.Extract(i, 5,rectangle);	# extract 5 pts (making up the closed rectangle)
			rectangle.ConvertForwardAffine(trans);
			# Set the line color
			gc.SetColorRGB(lineColor.red, lineColor.green, lineColor.blue, 100);
			gc.DrawPolyLine2(rectangle);
			# Set the fill color
			if (doFill)
			{
				gc.SetColorRGB(fillColor.red, fillColor.green, fillColor.blue, 100);
				gc.FillPolyLine2(rectangle);
			}
		}
	}
}
# Draw the names of the manholes
proc drawManholeNames(class GC gc, class POLYLINE surface, class STRINGLIST manholeNames, class COLOR textColor, string font, numeric pixelFontHeight)
{
	if (drawable && surface.GetNumPoints()>0)
	{
		gc.DrawTextSetFont(font);
		gc.DrawTextSetHeightPixels(pixelFontHeight);
		gc.DrawTextSetColors(textColor);
		# Draw all the manhole names at the manhole tops
		local numeric i;
		for (i=0; i<surface.GetNumPoints(); i++)
		{
			# get the position
			local class POINT2D linePoint = surface.GetVertex(i);
			if(!isNull(linePoint.y))
			{
				local class POINT2D topPoint = transPointToGraph(linePoint, surface);
				# draw the label
				string name = manholeNames.GetString(i);
				gc.TextStyle.Smoothing = 1;
				gc.DrawTextSimple(name, topPoint.x + pixelFontHeight, topPoint.y - 3, 90);
			}
		}
	}
}
# Draw the graph with the given polyline
proc drawManholes(class GC gc, class POLYLINE bottomLine, class POLYLINE surface, class COLOR lineColor)
{
	if (drawable && bottomLine.GetNumPoints()>0 && surface.GetNumPoints()>0)
	{
		# Draw the profile
		gc.SetColorRGB(lineColor.red, lineColor.green, lineColor.blue, 100);
		# Plot the rest of the points - connecting them as lines
		local numeric i;
		for (i=0; i<bottomLine.GetNumPoints(); i++)
		{
			local class POINT2D linePoint = bottomLine.GetVertex(i);
			local class POINT2D bottomPoint = transPointToGraph(linePoint, bottomLine);
			linePoint = surface.GetVertex(i);
			local class POINT2D topPoint = transPointToGraph(linePoint, bottomLine);
			# do the drawing
			gc.MoveTo(bottomPoint.x, topPoint.y);
			gc.DrawTo(bottomPoint.x, bottomPoint.y);
		}
	}
}
# Determine if the grid should be drawn or not
func doDrawGrid()
{
	return gridToggle.GetValue();
}
# Get the interval value for the x-dimension of the grid
func getGridIntervalX()
{
	# get the value entry then round or set appropriately
	local numeric xspacing = gridIntervalX.GetValue();
	if (abs(xspacing)<1)
	{
		xspacing=xspacing/abs(xspacing);
		gridIntervalX.SetValue(xspacing, 0);
	}
	else
	{
		xspacing = round(xspacing);
		gridIntervalX.SetValue(xspacing, 0);
	}
	return xspacing;
}
# Get the interval value for the y-dimension of the grid
func getGridIntervalY()
{
	# get the value entry then round or set appropriately
	local numeric yspacing = gridIntervalY.GetValue();
	if (abs(yspacing)<1)
	{
		yspacing=yspacing/abs(yspacing);
		gridIntervalY.SetValue(yspacing, 0);
	}
	else
	{
		yspacing = round(yspacing);
		gridIntervalY.SetValue(yspacing, 0);
	}
	return yspacing;
}
# Draw the grid
proc drawGrid(class GC gc, numeric xspacing, numeric yspacing, class POLYLINE bottomLine, class COLOR color)
{
	if (doDrawGrid() && drawable && bottomLine.GetNumPoints()>1)
	{
		# set the grid color
		gc.SetColorRGB(color.red, color.green, color.blue, 100);
		local class POINT2D pt1, pt2;
		# draw vertical lines
		local numeric length = bottomLine.GetVertex(bottomLine.GetNumPoints()-1).x;
		local class POINT2D bottomPoint;
		local class POINT2D topPoint;
		bottomPoint.y = graphMinZ;
		topPoint.y = graphMaxZ;
		for (bottomPoint.x = topPoint.x = xspacing; bottomPoint.x<=length; topPoint.x = bottomPoint.x = bottomPoint.x + xspacing)
		{
			# get graph coordinates
			local class POINT2D graphBottomPoint = transPointToGraph(bottomPoint, bottomLine);
			local class POINT2D graphTopPoint = transPointToGraph(topPoint, bottomLine);
			# do the drawing
			gc.MoveTo(graphTopPoint.x, graphTopPoint.y);
			gc.DrawTo(graphBottomPoint.x, graphBottomPoint.y);
		}
		# draw horizontal lines
		bottomPoint.x = 0;
		topPoint.x = length;
		bottomPoint.y = topPoint.y = graphMinZ;
		for (bottomPoint.y = topPoint.y = ceil(graphMinZ); bottomPoint.y<=graphMaxZ; topPoint.y = bottomPoint.y = bottomPoint.y + yspacing)
		{
			# get graph coordinates
			local class POINT2D graphBottomPoint = transPointToGraph(bottomPoint, bottomLine);
			local class POINT2D graphTopPoint = transPointToGraph(topPoint, bottomLine);
			# do the drawing
			gc.MoveTo(graphTopPoint.x, graphTopPoint.y);
			gc.DrawTo(graphBottomPoint.x, graphBottomPoint.y);
		}
	}
}
# Convert the polyline from obj to map coordinates
func class POLYLINE convertObjectToMap(class POLYLINE line)
{
	local class POLYLINE ret;
	local class POINT2D obj, map;
	local numeric i;
	# loop through the lines and convert from object to map coords
	for (i=0; i<line.GetNumPoints(); i++)
	{
		obj = line.GetVertex(i);
		map = ObjectToMap(lineVector, obj.x, obj.y, vecGeoref);
		ret.AppendVertex(map);
	}
	return ret;
}
# Determine if the polyline needs to be reversed
# (based on the start and end nodes of the lines)
func needToReverseLine(class POLYLINE origLine, class POLYLINE newLine, first, last)
{
	local class POINT2D point1, point2;
	# no reversal append to end - check first to avoid unnecessary flip (S2==E1)
	point1 = origLine.GetVertex(last);
	point2 = newLine.GetVertex(0);
	if (point1==point2) return 0;
	# no reversal append to front - check first to avoid unnecessary flip (S1==E2)
	point1 = origLine.GetVertex(first);
	point2 = newLine.GetVertex(newLine.GetNumPoints()-1);
	if (point1==point2) return 0;
	# reverse the new line (line2) for append to end (E1==E2)
	point1 = origLine.GetVertex(last);
	point2 = newLine.GetVertex(newLine.GetNumPoints()-1);
	if (point1==point2) return 1;
	# reverse the new line for append to front (S1==S2)
	point1 = origLine.GetVertex(first);
	point2 = newLine.GetVertex(0);
	if (point1==point2) return 1;
	# no reversal - all disjoint cases
	return 0;
}
# return 1 if should append polyline to end of line, 2 if should append polyline to front of line, 0 for no append
func doAppendEndPoints(class POINT2D start1, class POINT2D end1, class POINT2D start2, class POINT2D end2)
{
	if(end1 == start2) return 1;
	else if (end2 == start1) return 2;
	else return 0;
}
# return 1 if should append polyline to end of line, 2 if should append polyline to front of line, 0 for no append
func doAppend(class POLYLINE line, class POLYLINE polyline)
{
	class POINT2D lineStart = line.GetVertex(0);
	class POINT2D lineEnd = line.GetVertex(line.GetNumPoints()-1);
	class POINT2D polylineStart = polyline.GetVertex(0);
	class POINT2D polylineEnd = polyline.GetVertex(polyline.GetNumPoints()-1);
	return doAppendEndPoints(lineStart, lineEnd, polylineStart, polylineEnd);
}
# append the 'lineToAdd' to the 'line'
proc appendLine(class POLYLINE line, numeric newLine, numeric twoPointLine, numeric orderElements)
{
	# Get the line as a polyline
	local class POLYLINE polyline;
	polyline = GetVectorLine(lineVector, newLine);
	polyline = convertObjectToMap(polyline);
	if (twoPointLine)
	{
		if (polyline.GetNumPoints()>2) polyline.Straighten();
	}
	# if this is the first line just append and return
	if (line.GetNumPoints()==0)
	{
		line.Append(polyline);
		if (orderElements) orderedElemList.AddToFront(NumToStr(newLine));
		return;
	}
	# Check to see if we need to reverse the polyline
	local numeric reverse = needToReverseLine(line, polyline, 0, line.GetNumPoints()-1);
	if (reverse==1)
	{
		polyline.Reverse();
		reversedList.SetString("1", indexOf(elemList ,newLine));
	}
	# now simply append the line
	if (doAppend(line, polyline)==1)
	{
		# append to end
		line.Append(polyline);
		if (orderElements) orderedElemList.AddToEnd(NumToStr(newLine));
	}
	else if (doAppend(line, polyline)==2)
	{
		# append to front
		polyline.Append(line);
		line.Clear();
		line.Append(polyline);
		if (orderElements) orderedElemList.AddToFront(NumToStr(newLine));
	}
	else 
	{
		# can occur if loop is made, should clear selection
		PopupMessage("Cannot append disjoint line");
	}
}
# set the global graph offsets
proc setGraphOffsets(numeric fontHeight, numeric axisLabelOffset)
{
	# set the min and max display variables
	local string min$ = sprintf("%0.2f", graphMinZ);
	local string max$ = sprintf("%0.2f", graphMaxZ);
	if (graphMaxZ==-MAX_NUMBER) max$ = "null";
	if (graphMinZ==MAX_NUMBER) min$ = "null";
	if (graphMaxZ == graphMinZ) max$ = "";
	local numeric size = max(gc.TextGetWidth(max$), gc.TextGetWidth(min$));
	size = max(size, fontHeight+axisLabelOffset);
	leftGraphOffset = size+2;
}
# Function used to draw the graph
proc drawGraph(class POLYLINE pipeBottom, class POLYLINE pipeTop, class POLYLINE pipeFace, class POLYLINE surface, class POLYLINE demSurface, class POLYLINE smoothedSurface, class POLYLINE manholeDepth, class STRINGLIST manholeNames)
{
	# save pipe top and bottom for highlighting
	pipeBottomSave = pipeBottom;
	pipeTopSave = pipeTop;
	pipeFaceSave = pipeFace;
	# Draw the graph
	createGC();
	local class COLOR color;
	# set up the affine transformation for the graph
	setTrans(pipeBottom);
	# set up graph axes
	local string xlabel = "Distance (m)";
	local string ylabel = "Elevation (m)";
	local numeric drawTwoPointLines = 0;
	local numeric drawStartEndPoints = 1;
	# set the graph offsets - (globals)
	local numeric fontHeight = 12, axisLabelOffset=3;
	setGraphOffsets(fontHeight, axisLabelOffset);
	# fill in the background
	local class COLOR bgcolor;
	bgcolor.red = 90; bgcolor.green = 90; bgcolor.blue = 100;
	drawBackground(gc, bgcolor);
	# draw the grid
	color.red = 80; color.green = 80; color.blue = 80;
	drawGrid(gc, getGridIntervalX(), getGridIntervalY(), pipeBottom, color);
	# Draw and label the axes
	color.red = 0; color.green = 0; color.blue = 0;
	drawGraphAxes(gc, pipeBottom.GetVertex(pipeBottom.GetNumPoints()-1).x, xlabel, ylabel, drawTwoPointLines, color, fontHeight, axisLabelOffset);
	# draw the pipe bottom
	color = vectorLayer.SelectedElemColor;
#	drawPolyline(gc, pipeBottom, color);
	# draw the pipe top
#	drawPolyline(gc, pipeTop, color);
	# draw the pipe face
	local class COLOR fill = vectorLayer.SelectedElemColor;
#	fill.red = 40; fill.green = 40; fill.blue = 80;
	drawRectangles(gc, pipeFace, color, fill, fillToggle.GetValue());
	# draw the surface line (as dem or from DB)
	class POLYLINE manholeSurfaceLine;
	if (doUseDEM())
	{
		# draw smoothed dem surface line
		gc.DrawSetLineStyle("");
		color.red = 0; color.green = 0; color.blue = 0;
		drawPolyline(gc, smoothedSurface, color);
		manholeSurfaceLine = demSurface;
	}
	else
	{
		# draw surface line
		color.red = 0; color.green = 0; color.blue = 0;
		drawPolyline(gc, surface, color);
		manholeSurfaceLine = surface;
	}
	# draw the manholes
	color.red = 20; color.green = 80; color.blue = 20;
	drawManholes(gc, manholeDepth, manholeSurfaceLine, color);
	# draw the manhole labels
	color.red = 0; color.green = 0; color.blue = 0;
	drawManholeNames(gc, manholeSurfaceLine, manholeNames, color, "ARIAL", 12);
	canvas.Refresh(1);
}
# Called when the min or max z value is changed
proc OnChangeGraphZ()
{
	graphMinZ = graphMinSetting.GetValue();
	graphMaxZ = graphMaxSetting.GetValue();
	needsRedraw = 1;
}
# Callback for a right mouse button press
proc OnRightButtonPress()
{
	# make sure z settings are current
	OnChangeGraphZ();
	# clear the line
	demLine.Clear();
	orderedElemList.Clear();
	# Get the first line as a polyline
	class POLYLINE finalLine;
	# Get all of the lines, appending appropriately
	local numeric i;
	for (i=0; i<elemList.GetNumItems(); i++)
	{
		appendLine(finalLine, getElement(elemList, i), 1, 1);
		appendLine(demLine, getElement(elemList, i), 0, 0);
	}
	# Get the line to graph - x-dimension is distance, y is elevation
	local class POLYLINE pipeBottom = constructPipeBottom(finalLine);
	# Create the pipe top line - bottom + pipeHeight
	local class POLYLINE pipeTop = constructPipeTop(pipeBottom, finalLine);
	# Create the pipe face - defines the area between teh pipeBottom and pipeTop
	local class POLYLINE pipeFace = constructPipeFace(pipeBottom, pipeTop);
	# Create the surface line
	local class POLYLINE surface = constructSurface(finalLine);
	# Create the manhole depth line
	local class POLYLINE manholeDepth = constructManholeDepth(pipeBottom, finalLine);
	# Create the manhole labels
	local class STRINGLIST manholeNames = constructManholeNames(finalLine);
	# Create the dem surface line
	if (doUseDEM() && demSet)
	{
		local class POLYLINE demSurface = constructDemSurface(demLine);
		local class POLYLINE smoothedSurface = constructSmoothSurface(demLine, LinScale(dem), ColScale(dem), 3);
	}
	drawable = 1;
	currentlyActive = -1;
	drawGraph(pipeBottom, pipeTop, pipeFace, surface, demSurface, smoothedSurface, pipeBottom, manholeNames);
	needsRedraw=0;
}
# Return a number > 0 if one of the two nodes matches an end node.
# case 1: node1==endNodes[1] return 1
# case 2: node1==endNodes[2] return 2
# case 3: node2==endNodes[1] return 3
# case 4: node2==endNodes[2] return 4
func matchesEndNodes(numeric node1, numeric node2)
{
	if (node1==endNodes[1])	# S1 == S2
	{
		endNodes[1] = node2;
		return 1;
	}
	if (node1==endNodes[2])	# E1 == S2
	{
		endNodes[2] = node2;
		return 2;
	}
	if (node2==endNodes[1])	# S1 == E2
	{
		endNodes[1] = node1;
		return 3;
	}
	if (node2==endNodes[2])	# E1 == E2
	{
		endNodes[2] = node1;
		return 4;
	}
	return 0;
}
# if list has 0 -> 1 else -> 0
proc reverseBits(class STRINGLIST s)
{
	local numeric i;
	for (i=0; i<s.GetNumItems(); i++)
	{
		if (s.GetString(i)=="0") s.SetString("1", i);
		else s.SetString("0", i);
	}
}
# Toggle the line element given the element number
proc toggleClosestLineElement(numeric elemNum)
{
	# check the endnodes for a match
	local numeric node1 = lineVector.LINE[elemNum].Internal.StartNode;
	local numeric node2 = lineVector.LINE[elemNum].Internal.EndNode;
	local numeric index = indexOf(elemList, elemNum);
	local numeric matchNum = matchesEndNodes(node1, node2);
	if (index>=0)
	{
		local numeric doRemove = 0;
		# check to see if the current line is removable
		if (matchNum>0) doRemove = 1;
		if (doRemove)
		{
			# remove the line
			vectorLayer.Line.HighlightSingle(elemNum, "Subtract");
			elemList.Remove(index);
			reversedList.Remove(index);
			if (reversedList.GetString(0)=="1") reverseBits(reversedList);
			if(vectorLayer.Line.GetSelectedElement()==0)
			{
				endNodes[1] = -1;
				endNodes[2] = -1;
			}
		}
	}
	else
	{
		# if no lines exist, then simply append
		if (endNodes[1]==-1 && endNodes[2]==-1)
		{
			endNodes[1] = node1;
			endNodes[2] = node2;
			vectorLayer.Line.HighlightSingle(elemNum, "Add");
			elemList.AddToEnd(NumToStr(elemNum));
			reversedList.AddToEnd("0");
		}
		else if (matchNum>0) # check to see if the current line is appendable
		{
			# add the line
			vectorLayer.Line.HighlightSingle(elemNum, "Add");
			elemList.AddToEnd(NumToStr(elemNum));
			reversedList.AddToEnd("0");
		}
	}
}
# Toggle the line element closest to the point 'position'
func toggleClosestLine(class POINT2D position)
{
	local numeric elemNum = FindClosestLine(lineVector, position.x, position.y);
	if (elemNum > 0 )
	{
		toggleClosestLineElement(elemNum);
	}
	return elemNum;
}
# Callback for a left mouse button press
proc OnLeftButtonPress()
{
	# Find cursor position in screen coordinates
	local class POINT2D point;
	point.x = PointerX;
	point.y = PointerY;
	point = TransPoint2D(point, ViewGetTransLayerToScreen(View, vectorLayer, 1));
	# toggle the element as selected (if valid) or deselected (if possible)
	toggleClosestLine(point);
}
# Called when the close button is pressed.  Closes the dialogs.
proc cbClose()
{
	dlgwin.Close(0);
	if (setDefaultWhenClose)
	{
		setDefaultWhenClose = false;
		View.SetDefaultTool();
	}
}
# Called when the canvas is resized - gc is recreated before drawing
proc OnCanvasResize(class GUI_CANVAS canvas, numeric width, numeric height)
{
	OnRightButtonPress();
}
# return the element number of the nearest line to the graph point
func getNearestLineElementFromGraph(class POINT2D graphPoint)
{
	# make sure z settings are current
#	graphMinZ = graphMinSetting.GetValue();
#	graphMaxZ = graphMaxSetting.GetValue();
	local class POLYLINE splits;
	local class POINT2D tmp;
	local numeric distance = 0;
	tmp.x = distance;
	tmp.y = 0;
	splits.AppendVertex(tmp);
	local numeric i=0;
	for (i=1; i<finalLine.GetNumPoints(); i++)
	{
		local class POINT2D point1, point2;
		point1 = finalLine.GetVertex(i-1);
		point2 = finalLine.GetVertex(i);
		local numeric dx = computeDistance(point1, point2);
		distance += dx;
		tmp.x = distance;
		tmp.y = 0;
		splits.AppendVertex(tmp);
	}
	local class RECT rect = getGraphExtents();
	# Get the drawing scale
	local numeric xscale = 0;
	xscale = (rect.x2 - rect.x1) / distance;
	# apply scales and offsets to get graph coords
	for (i=1; i<splits.GetNumPoints(); i++)
	{
		tmp = splits.GetVertex(i);
		tmp.x = tmp.x * xscale + leftGraphOffset;
		splits.SetVertex(i, tmp);
	}
	graphPoint.y = 0; # ignore y axis
	local numeric vertexNum = splits.FindClosestVertex(graphPoint);
	tmp = splits.GetVertex(vertexNum);
	# take care of duplicate vertex locations (which will choose first one)
	if (graphPoint.x > tmp.x)
	{
		vertexNum++;
	}
	if (vertexNum >= splits.GetNumPoints() || (vertexNum==1 && graphPoint.x < leftGraphOffset))
	{
		return 0;
	}
	# get the line from the vertex
	local numeric lineNum = floor(vertexNum/2);
	local numeric elemNum = getElement(orderedElemList, lineNum);
#	vertexDisplay.SetValue(vertexNum, 0);
#	lineDisplay.SetValue(lineNum, 0);
#	elemDisplay.SetValue(elemNum, 0);
	return elemNum;
}
proc highlightGraphSegment(numeric lineNum, class COLOR color)
{
	local class POLYLINE myrect;
	pipeFaceSave.Extract(lineNum*5, 5, myrect);
	drawRectangles(gc, myrect, color, color, fillToggle.GetValue());
	# draw the manholes
	color.red = 20; color.green = 80; color.blue = 20;
	drawManholes(gc, pipeBottomSave, pipeTopSave, color);
	canvas.Refresh(1);
}
proc makeLineActive(numeric elemNum)
{
	vectorLayer.Line.SetActiveElement(elemNum);
	local numeric lineOrdering = indexOf(orderedElemList, elemNum);
	if (lineOrdering>-1 && currentlyActive != lineOrdering)
	{
		local class COLOR color = vectorLayer.SelectedElemColor;
		if(currentlyActive>-1) highlightGraphSegment(currentlyActive, color);
		currentlyActive = lineOrdering;
#color.red = 90; color.green = 90; color.blue = 100;
#highlightGraphSegment(currentlyActive, color);
		color = vectorLayer.ActiveElemColor;
		highlightGraphSegment(currentlyActive, color);
		materialDisplay.SetValue(readLineTableRecordStr(elemNum, "CAN_MAT"), 0);
		slopeDisplay.SetValue(readLineTableRecord(elemNum, "CAN_SLOPE"), 0);
		diameterDisplay.SetValue(readLineTableRecord(elemNum, "CAN_DIAM")/1000, 0);
	}
}
func getNearestLineElementFromGraph2(class POINT2D graphPoint)
{
	# make sure z settings are current
#	graphMinZ = graphMinSetting.GetValue();
#	graphMaxZ = graphMaxSetting.GetValue();
	local class POLYLINE splits = pipeBottomSave;
	local class POINT2D tmp;
#	local numeric distance = 0;
#	tmp.x = distance;
#	tmp.y = 0;
#	splits.AppendVertex(tmp);
#
#	local numeric i=0;
#	for (i=1; i<finalLine.GetNumPoints(); i++)
#	{
#		local class POINT2D point1, point2;
#		point1 = finalLine.GetVertex(i-1);
#		point2 = finalLine.GetVertex(i);
#		local numeric dx = computeDistance(point1, point2);
#		distance += dx;
#
#		tmp.x = distance;
#		tmp.y = 0;
#		splits.AppendVertex(tmp);
#	}
#
#	local class RECT rect = getGraphExtents();
#
#	# Get the drawing scale
#	local numeric xscale = 0;
#	xscale = (rect.x2 - rect.x1) / distance;
	# apply scales and offsets to get graph coords
	local numeric i;
	for (i=1; i<splits.GetNumPoints(); i++)
	{
#		tmp = splits.GetVertex(i);
#		tmp.x = tmp.x * xscale + leftGraphOffset;
		tmp = trans.ConvertPoint2DFwd(splits.GetVertex(i));
		tmp.y = 0;
		splits.SetVertex(i, tmp);
	}
	graphPoint.y = 0; # ignore y axis
	local numeric vertexNum = splits.FindClosestVertex(graphPoint);
	tmp = splits.GetVertex(vertexNum);
	# take care of duplicate vertex locations (which will choose first one)
	if (graphPoint.x > tmp.x)
	{
		vertexNum++;
	}
	if (vertexNum >= splits.GetNumPoints() || (vertexNum==1 && graphPoint.x < leftGraphOffset))
	{
		return 0;
	}
	# get the line from the vertex
	local numeric lineNum = floor(vertexNum/2);
	local numeric elemNum = getElement(orderedElemList, lineNum);
#	vertexDisplay.SetValue(vertexNum, 0);
#	lineDisplay.SetValue(lineNum, 0);
#	elemDisplay.SetValue(elemNum, 0);
	return elemNum;
}
# Called when the mouse is moved over the canvas - highlights the nearest line in the 2d view
proc OnCanvasMouseMove(class GUI_CANVAS canvas, class POINT2D point, numeric shift, numeric ctrl)
{
#<debug>
local class POINT2D mapPoint = trans.ConvertPoint2DInv(point);
if (!IsNull(mapPoint.x) && !IsNull(mapPoint.y))
{
	mouseXDisplay.SetValue(mapPoint.x,0);
	mouseYDisplay.SetValue(mapPoint.y,0);
}
#</debug>
	if (needsRedraw)
	{
		OnRightButtonPress();
	}
	local numeric elemNum = getNearestLineElementFromGraph(point);
	if (elemNum > 0)
	{
		makeLineActive(elemNum);
	}
	needsRedraw = 0;
}
# Called when mouse is moved over the 2D view - highlights the nearest line in the 2d view
proc OnPointerMoveNoButton()
{
	if (needsRedraw)
	{
		OnRightButtonPress();
		needsRedraw = 0;
	}
	local class POINT2D pointer;
	pointer.x = PointerX;
	pointer.y = PointerY;
	# Get the cursor position in map coords
	local class TRANSPARM screenToView = ViewGetTransViewToScreen(View, 1);
	local class TRANSPARM viewToLayer = ViewGetTransLayerToView(View, vectorLayer, 1);
	pointer = TransPoint2D(pointer, screenToView);
	pointer = TransPoint2D(pointer, viewToLayer);
	local numeric elemNum = FindClosestLine(lineVector, pointer.x, pointer.y);
	if (elemNum > 0)
	{
		makeLineActive(elemNum);
	}
}
# Called when the button to clear the lines is pressed
proc OnClearLines()
{
	endNodes[1]=-1;
	endNodes[2]=-1;
	elemList.Clear();
	orderedElemList.Clear();
	reversedList.Clear();
	vectorLayer.UnhighlightAllElements(1);
	materialDisplay.SetValue("", 0);
	slopeDisplay.SetValue(0, 0);
	diameterDisplay.SetValue(0, 0);
	mouseXDisplay.SetValue(0, 0);
	mouseYDisplay.SetValue(0, 0);
	OnRightButtonPress();
}
# Called with the grid toggle button is pressed, does a full redraw
proc OnGridTogglePressed()
{
	OnRightButtonPress();
}
# Called with the fill toggle button is pressed, does a full redraw
proc OnFillTogglePressed()
{
	OnRightButtonPress();
}
# Called with the grid toggle button is pressed, checks that dem exists
proc OnDemTogglePressed()
{
	checkLayer();
}
# Called the first time the tool is activated.
# If the tool implements a dialog it should be created (but not displayed) here.
func OnInitialize()
{
	# initialize the end node values, used to validate line appends
	endNodes[1]=-1;
	endNodes[2]=-1;
	# handle as layout or as group
	if (Layout)
	{
		WidgetAddCallback(Layout.GroupSelectedCallback, cbGroup);
		activegroup = Layout.ActiveGroup;
	}
	else activegroup = Group;
	WidgetAddCallback(activegroup.LayerSelectedCallback, cbLayer);
	# define the dialog here with xml specification
	string xml$ = 
	'<?xml version="1.0"?>
	<!DOCTYPE root SYSTEM "smlforms.dtd">
	<root>
		<dialog id="guicanvas" Title="Line Profile" Buttons="">
			<groupbox Name=" Graph elevation display settings: " ExtraBorder="4" HorizResize="Fixed" VertResize="Fixed">
				<pane Orientation="horizontal">
					<togglebutton id="demtoggle" Name="Use DEM"/>
					<pane ChildSpacing="0" VertAlign="Top">
						<label>Minimum Z:</label>
						<editnumber id="minz" BlankZero="false" Precision="0" ReadOnly="false" Width="5" Height="10" Default="0" OnChanged="OnChangeGraphZ()"/>
					</pane>
					<pane ChildSpacing="0" VertAlign="Bottom">
						<label>Maximum Z:</label>
						<editnumber id="maxz" BlankZero="false" Precision="0" ReadOnly="false" Width="5" Height="10" Default="25" OnChanged="OnChangeGraphZ()"/>
					</pane>
				</pane>
			</groupbox>
			<groupbox Name=" Grid display settings: " ExtraBorder="4" HorizResize="Fixed" VertResize="Fixed">
				<pane Orientation="horizontal">
					<togglebutton id="gridtoggle" Name="Draw Grid"/>
					<pane ChildSpacing="0" VertAlign="Top">
						<label>X-Interval:</label>
						<editnumber id="xinterval" BlankZero="false" Precision="0" ReadOnly="false" Width="5" Height="10" Default="10"/>
					</pane>
					<pane ChildSpacing="0" VertAlign="Bottom">
						<label>Y-Interval:</label>
						<editnumber id="yinterval" BlankZero="false" Precision="0" ReadOnly="false" Width="5" Height="10" Default="1"/>
					</pane>
				</pane>
			</groupbox>
			<pane>
				<canvas id="canvas" ExtraBorder="4" Width="400" Height="300"/>
			</pane>
			<groupbox Name=" Pipe Attributes: " ExtraBorder="4" HorizResize="Fixed" VertResize="Fixed">
				<pane Orientation="horizontal" HorizAlign="Center">
					<pane ChildSpacing="0" HorizAlign="Left">
						<label>Pipe Material:</label>
						<edittext id="material" ReadOnly="true" Width="5"/>
					</pane>
					<pane ChildSpacing="0" HorizAlign="Left">
						<label>Pipe Diameter:</label>
						<editnumber id="diameter" BlankZero="true" Precision="3" ReadOnly="true" Width="5" Height="10" Default="0"/>
						<label>meters</label>
					</pane>
					<pane ChildSpacing="0" HorizAlign="Left">
						<label>Pipe Slope:</label>
						<editnumber id="slope" BlankZero="false" Precision="2" ReadOnly="true" Width="5" Height="10" Default="0"/>
					</pane>
				</pane>
			</groupbox>
			<pane Orientation="horizontal" HorizAlign="Center">
				<groupbox Name=" Coordinate Display: " ExtraBorder="4" HorizResize="Fixed" VertResize="Fixed">
					<pane Orientation="horizontal" HorizAlign="Center">
						<pane ChildSpacing="0" HorizAlign="Left">
							<label>Distance:</label>
							<editnumber id="mousex" BlankZero="false" Precision="2" ReadOnly="true" Width="5" Height="10" Default="0" MinVal="0" />
						</pane>
						<pane ChildSpacing="0" HorizAlign="Left">
							<label>Elevation:</label>
							<editnumber id="mousey" BlankZero="false" Precision="2" ReadOnly="true" Width="5" Height="10" Default="0"/>
						</pane>
					</pane>
				</groupbox>
				<groupbox Name=" Pipe Style Settings: " ExtraBorder="4" HorizResize="Fixed" VertResize="Fixed">
					<pane Orientation="horizontal">
						<togglebutton id="filltoggle" Name="Fill Pipe Polygons"/>
					</pane>
				</groupbox>
			</pane>
<!--
				<pane Orientation="horizontal" HorizAlign="Center">
					<pane ChildSpacing="0" HorizAlign="Left">
						<label>VertexNum:</label>
						<editnumber id="vertex" BlankZero="false" Precision="0" ReadOnly="true" Width="5" Height="10" Default="0"/>
					</pane>
					<pane ChildSpacing="0" HorizAlign="Left">
						<label>LineNum:</label>
						<editnumber id="line" BlankZero="false" Precision="0" ReadOnly="true" Width="5" Height="10" Default="0"/>
					</pane>
					<pane ChildSpacing="0" HorizAlign="Left">
						<label>ElemNum:</label>
						<editnumber id="elem" BlankZero="true" Precision="0" ReadOnly="true" Width="5" Height="10" Default="0"/>
					</pane>
				</pane>
-->
			<pane Orientation="horizontal" HorizAlign="Center" HorizResize="Fixed" VertResize="Fixed">
				<pushbutton id="clearlines" Name="Clear Selected Lines" OnPressed="OnClearLines()"/>
			</pane>
		</dialog>
	</root>
	';
	# Parse the xml dialog specification
	local class XMLDOC dlgdoc;
	local numeric err = dlgdoc.Parse(xml$);
	if (err < 0) {
		PopupError(err);
		Exit();
		}
	local string dlgid$ = "guicanvas";
	local class XMLNODE dlgnode = dlgdoc.GetElementByID(dlgid$);
	if (dlgnode == 0) {
		PopupMessage("Could not find specified id: "+dlgid$);
		Exit();
		}
	dlgwin.SetXMLNode(dlgnode);
	# create the dialog as a modeless dialog
	err = dlgwin.CreateModeless();
	if (err < 0) {
		PopupError(err);
		Exit();
		}
	# get the control for the drawing canvas
	canvas = dlgwin.GetCtrlByID("canvas");
	canvas.SetOnSize(OnCanvasResize);
	canvas.SetOnRightDown(OnCanvasResize);
	canvas.SetOnMouseMove(OnCanvasMouseMove);
	# get the control for the grid toggle
	gridToggle = dlgwin.GetCtrlByID("gridtoggle");
	gridToggle.SetValue(1,0);
	gridToggle.SetOnPressed(OnGridTogglePressed);
	# get the control for the dem toggle
	demToggle = dlgwin.GetCtrlByID("demtoggle");
	demToggle.SetOnPressed(OnDemTogglePressed);
	# get the controls for the grid interval settings
	gridIntervalX = dlgwin.GetCtrlByID("xinterval");
	gridIntervalY = dlgwin.GetCtrlByID("yinterval");
	# get the controls for the min and max z setting
	graphMinSetting = dlgwin.GetCtrlByID("minz");
	graphMaxSetting = dlgwin.GetCtrlByID("maxz");
	OnChangeGraphZ();
	# get the control for the grid toggle
	fillToggle = dlgwin.GetCtrlByID("filltoggle");
	fillToggle.SetValue(1,0);
	fillToggle.SetOnPressed(OnFillTogglePressed);
	# get control for display of info
#	vertexDisplay = dlgwin.GetCtrlByID("vertex");
#	lineDisplay = dlgwin.GetCtrlByID("line");
#	elemDisplay = dlgwin.GetCtrlByID("elem");
	mouseXDisplay = dlgwin.GetCtrlByID("mousex");
	mouseYDisplay = dlgwin.GetCtrlByID("mousey");
	materialDisplay = dlgwin.GetCtrlByID("material");
	slopeDisplay = dlgwin.GetCtrlByID("slope");
	diameterDisplay = dlgwin.GetCtrlByID("diameter");
	drawable = 0;
}
 
# Called when tool is to be destroyed, will not be called if tool was never activated.
func OnDestroy()
{
	dlgwin.Close(0);
}
# Called when tool is activated.
func OnActivate()
{
	checkLayer();
	# open the graph dialog window
	dlgwin.Open();
	# draw the graph
	OnRightButtonPress();
	setDefaultWhenClose = true;
}
 
# Called when tool is deactivated (usually when switching to another tool).
func OnDeactivate()
{
	setDefaultWhenClose = false;
	cbClose();
}