# PipeProfileCAD.sml
#
# 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.
# Only allows selection of contiguous connected lines, but can add to
# either end of selected line set. Active elements are highlighted in
# both the View and Line Profile window on mouseover in either window.
#
# Usage:
# Left click: toggle select/deselect line
# Right click: Accept current line and draw
# Save As button on Line Profile window saves the current graph to a CAD object.
#
# Requires TNTmips 7.0 dated 1 March 2005 or later.
## MicroImages, Inc.
## Revised 19 April 2005. Fixed inconsistency in placement of graph axis labels in
## Line Profile window and CAD.
# GUI global definitions
class GUI_DLG dlgwin;
class GUI_CANVAS canvas;
class GC gc; # global graphics context for drawing canvas in dialog
class GRDEVICE_CAD deviceCAD;
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;
class CAD CADgraph;
# Graphical display global definitions
class POLYLINE pipeBottom, pipeTop, pipeFace, surface;
class POLYLINE surface, manholeDepth, demSurface, smoothedSurface;
class STRINGLIST manholeNames;
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$, 2, graphy[1]+fontHeight/2);
gc.DrawTextSimple(min$, 2, 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(class GC gc, 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";
# PopupMessage( sprintf("font height = %d", NumToStr(fontHeight) ) );
if (graphMaxZ == graphMinZ) max$ = "";
local numeric size = max(gc.TextGetWidth(max$), gc.TextGetWidth(min$));
# PopupMessage( sprintf("max$ width = %d, min$ width = %d, size = %d", gc.TextGetWidth(max$), gc.TextGetWidth(min$), size) );
size = max(size, fontHeight+axisLabelOffset);
leftGraphOffset = size - 4;
# PopupMessage( sprintf("size = %d, height+offset = %d, leftGraphOffset = %d", size, fontHeight+axisLabelOffset, leftGraphOffset) );
}
# Procedure used to draw the graph; passed a special GC
# created by the following procedure.
proc drawGraph()
{
# save pipe top and bottom for highlighting
pipeBottomSave = pipeBottom;
pipeTopSave = pipeTop;
pipeFaceSave = pipeFace;
# create graphics context for graph
createGC();
gc.DrawTextSetFont("ARIAL");
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(gc, fontHeight, axisLabelOffset);
# fill in the background
local class COLOR bgcolor;
bgcolor.red = 98; bgcolor.green = 98; bgcolor.blue = 98;
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); # refresh the drawing canvas
}
# Procedure for drawing graph in CAD object; passed a local GC
# when called by the OnSaveGraph() procedure
proc drawGraphForCAD(class GC gc)
{
local class COLOR color;
gc.DrawTextSetFont("ARIAL");
# 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(gc, fontHeight, axisLabelOffset);
# fill in the background
local class COLOR bgcolor;
bgcolor.red = 98; bgcolor.green = 98; bgcolor.blue = 98;
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);
}
# Procedure called when Save Graph as CAD button on dialog is pressed
proc OnSaveGraph()
{
GetOutputCAD(CADgraph); # prompt user to select output CAD object
# create GREDEVICE for drawing into CAD object
deviceCAD.Create(CADgraph , getHeight(), getWidth() );
# create graphics context for the device
local class GC gcCAD;
gcCAD = deviceCAD.CreateGC();
# call procedure to draw the graph for the CAD object
drawGraphForCAD(gcCAD);
deviceCAD.Close(); # close CAD graphics device
CloseCAD(CADgraph); # close CAD object
}
# 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
pipeBottom = constructPipeBottom(finalLine);
# Create the pipe top line - bottom + pipeHeight
pipeTop = constructPipeTop(pipeBottom, finalLine);
# Create the pipe face - defines the area between the pipeBottom and pipeTop
pipeFace = constructPipeFace(pipeBottom, pipeTop);
# Create the surface line
surface = constructSurface(finalLine);
# Create the manhole depth line
manholeDepth = constructManholeDepth(pipeBottom, finalLine);
# Create the manhole labels
manholeNames = constructManholeNames(finalLine);
# Create the dem surface line
if (doUseDEM() && demSet)
{
demSurface = constructDemSurface(demLine);
smoothedSurface = constructSmoothSurface(demLine, LinScale(dem), ColScale(dem), 3);
}
drawable = 1;
currentlyActive = -1;
drawGraph();
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()"/>
<pushbutton id="saveGraph" Name="Save Graph as CAD" OnPressed="OnSaveGraph()"/>
</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();
}