Syntax Highlighing:
comments, key words, predefined symbols, class members & methods, functions & classes
# LineProfileGraphTip.sml
#
# This script is meant to be run as a Display control script
#
# Assumptions:
# In the group there are at least two layers:
# Layer 1: DEM (first layer in group)
# Layer 2: Vector object with lines (last layer in group)
#
# Purpose:
# The script generates a graphical image tip displaying a plotted
# elevation profile of the nearest line, using the DEM values for elevation.
#
# TODO:
# If the extents of the nearest line (for which the graph tip is
# generated) is beyond the extents of the view window, then the
# offset value must be adjusted.
## Global declarations
class GRDEVICE_MEM_RGB24 imagedev;
class GRDEVICE_MEM_BINARY maskdev;
class GC gc;
class GRE_LAYER_VECTOR vectorLayer;
class GRE_LAYER_RASTER rasterLayer;
class VECTOR lineVector;
class RASTER dem;
class GEOREF vecGeoref;
class TRANSPARM objToMap;
numeric minz, maxz;
numeric leftGraphOffset, bottomGraphOffset=20, rightGraphOffset=5, topGraphOffset=15;
numeric fontHeight = 12;
# Initialize the image device
proc OnInitialize ()
{
imagedev.Create(192, 256);
maskdev.Create(192, 256);
maskdev.ClearAll();
}
# Get the raster and vector layers (it is assumed to be a vector overlaying a DEM)
proc OnGroupCreateView (class GRE_GROUP group)
{
# Raster is assumed to be first layer
rasterLayer = group.FirstLayer;
DispGetRasterFromLayer(dem, rasterLayer);
# Vector is assumed to be last layer
vectorLayer = group.LastLayer;
DispGetVectorFromLayer(lineVector, vectorLayer);
vecGeoref = GetLastUsedGeorefObject(lineVector);
class TRANSPARM mapTrans;
mapTrans.InputProjection = vectorLayer.Projection;
mapTrans.OutputProjection = rasterLayer.Projection;
}
# 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 z value
func computeElevation(class POINT2D p)
{
p = mapTrans.ConvertPoint2DFwd(p); # convert from vector map coordinates to raster map coordinates
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;
}
# Construct the graph line, x dimension is line distance y is elevation
func class POLYLINE constructGraphLine(class POLYLINE origLine)
{
local class POLYLINE newLine;
local class POINT2D tmp;
local numeric i;
local numeric distance=0;
local numeric elevation = computeElevation(origLine.GetVertex(0));
tmp.x = distance;
tmp.y = elevation;
newLine.AppendVertex(tmp);
for i=1 to origLine.GetNumPoints()-1
{
distance += computeDistance(origLine.GetVertex(i-1), origLine.GetVertex(i));
elevation = computeElevation(origLine.GetVertex(i));
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 == NullValue(dem);
}
# Get the width from the appropriate drawing device
func getHeight()
{
return imagedev.GetHeight();
}
# Get the width from the appropriate drawing device
func getWidth()
{
return imagedev.GetWidth();
}
# Create the GC here using the appropriate drawing device (gc is global)
proc createGC()
{
if (gc == 0) gc = imagedev.CreateGC();
}
# procedure to draw the axes for the graph
proc drawGraphAxes(class POLYLINE graphLine)
{
# make a greyish-blue background
local class COLOR textColor;
gc.DrawTextSetColors(textColor);
gc.SetColorRGB(255, 255, 255);
gc.FillRect(0, 0, getWidth(), getHeight());
gc.SetColorRGB(0, 0, 0);
gc.DrawRect(0,0, getWidth() - 1, getHeight() - 1);
# Get the minimum and maximum z values
minz=9999999; maxz=-9999999;
local numeric i=0;
for i=0 to graphLine.GetNumPoints()-1
{
local numeric z = graphLine.GetVertex(i).y;
if (z < minz) minz = z;
if (z > maxz) maxz = z;
}
local string min$ = sprintf("%d", minz);
local string max$ = sprintf("%d", maxz);
if (maxz==-9999999) max$ = "null";
if (minz==9999999) min$ = "null";
# Draw graph axes
local numeric size = gc.TextGetWidth(max$);
leftGraphOffset = size + 5;
if (maxz == minz) 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 text for coordinate and elevation axis labels
gc.DrawTextSetFont("ARIAL");
gc.DrawTextSetHeightPixels(fontHeight);
# draw y axis labels
gc.DrawTextSimple(max$, 3, graphy[1]+fontHeight/2);
gc.DrawTextSimple(min$, 3, graphy[2]+fontHeight/2);
local string ylabel = "elevation (m)";
gc.DrawTextSimple(ylabel, graphx[1]-4, (graphy[3]-graphy[1])/2+gc.TextGetWidth(ylabel)*3/4, 90);
# draw x axis labels
gc.DrawTextSimple("0", graphx[1] - gc.TextGetWidth("0")/2, getHeight()-4);
local string str$ = sprintf("%d", graphLine.GetVertex(graphLine.GetNumPoints()-1).x);
gc.DrawTextSimple(str$, getWidth() - gc.TextGetWidth(str$) - 3, getHeight()-4);
local string xlabel = "distance (m)";
gc.DrawTextSimple(xlabel, (graphx[3]-graphx[1])/2-gc.TextGetWidth(xlabel)/4, getHeight()-bottomGraphOffset+fontHeight+1);
}
# function to translate a point on the graphline to image device coordinates for drawing
func class POINT2D transPointToGraph(class POINT2D point, class POLYLINE graphLine)
{
# 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 (maxz != minz) yscale = (maxy - miny) / (maxz - minz);
point.x = point.x * xscale + leftGraphOffset;
if (yscale!=0) point.y = getHeight() - ((point.y-minz) * yscale + bottomGraphOffset);
else point.y = getHeight() - bottomGraphOffset;
return point;
}
# procedure to draw the graph with the given polyline
proc drawGraph(class POLYLINE graphLine, numeric vertexNum)
{
# Plot out the axes first
drawGraphAxes(graphLine);
# Set profile color and plot point zero
gc.SetColorRGB(200, 50, 50);
local class POINT2D linePoint = graphLine.GetVertex(0);
local class POINT2D graphPoint = transPointToGraph(linePoint, graphLine);
gc.DrawPoint(graphPoint.x, graphPoint.y);
# Plot the rest of the profile vertices
local numeric i;
for i=1 to graphLine.GetNumPoints()-1
{
linePoint = graphLine.GetVertex(i);
graphPoint = transPointToGraph(linePoint, graphLine);
if (isNull(linePoint.y))
{
# if null skip point and move to next
linePoint = graphLine.GetVertex(i+1);
graphPoint = transPointToGraph(linePoint, graphLine);
gc.MoveTo(graphPoint.x, graphPoint.y);
}
else gc.DrawTo(graphPoint.x, graphPoint.y);
}
# Draw crosshairs marking the point on the graph nearest to cursor
local class COLOR horizLineColor, vertLineColor;
horizLineColor.red = 20;
horizLineColor.green = 20;
horizLineColor.blue = 80;
vertLineColor.red = 20;
vertLineColor.green = 20;
vertLineColor.blue = 80;
local class POINT2D graphCircle = graphLine.GetVertex(vertexNum);
graphCircle = transPointToGraph(graphCircle, graphLine);
# draw the vertical line
gc.SetColorRGB(vertLineColor.red, vertLineColor.green, vertLineColor.blue, 100);
gc.MoveTo(graphCircle.x, topGraphOffset);
gc.DrawTo(graphCircle.x, getHeight() - bottomGraphOffset);
# draw label
gc.DrawTextSetColors(vertLineColor);
string dist = NumToStr(int(graphLine.GetVertex(vertexNum).x));
local numeric x, y;
# set x position of measurement label
if(graphCircle.x < (leftGraphOffset + gc.TextGetWidth(dist) + 1)) x = graphCircle.x + 1; # draw on right
else x = graphCircle.x - gc.TextGetWidth(dist) - 1; # draw on left
# set y position of measurement label
y = topGraphOffset + fontHeight;
# draw distance measurement label
gc.DrawTextSimple(dist, x, y);
# draw horizontal line
if (!isNull(graphCircle.y))
{
# set the line color
gc.SetColorRGB(horizLineColor.red, horizLineColor.green, horizLineColor.blue, 100);
# draw line
gc.MoveTo(leftGraphOffset, graphCircle.y);
gc.DrawTo(getWidth() - rightGraphOffset, graphCircle.y);
# draw label
gc.DrawTextSetColors(horizLineColor);
string elev = NumToStr(graphLine.GetVertex(vertexNum).y);
if (graphCircle.x > (getWidth() - rightGraphOffset - leftGraphOffset)/2)
{
gc.DrawTextSimple(elev, leftGraphOffset+1, graphCircle.y-1); # draw on left
}
else
{
gc.DrawTextSimple(elev, getWidth() - rightGraphOffset - gc.TextGetWidth(elev)-1, graphCircle.y-1); # draw on right
}
}
}
# Computes the offset to use to prevent graph from obscuring element
func class POINT2D computeOffset(class POLYLINE line, class GRE_VIEW view, class POINT2D cursor)
{
local class POINT2D offset, imageOffset, extentsOffset, center;
local class RECT extents = line.ComputeExtents();
local numeric isUpper = 0, isLeft = 0;
local numeric pixelOffset = 5;
center = view.Center;
center = TransPoint2D(center, ViewGetTransViewToScreen(view));
if (cursor.y < center.y) isUpper = 1;
if (cursor.x < center.x) isLeft = 1;
if (isUpper) # isUpper half of view, get min y
{
if (extents.pt1.y < extents.pt2.y) offset.y = extents.pt1.y;
else offset.y = extents.pt2.y;
extentsOffset.y = pixelOffset;
}
else # isLower half of view, get max y
{
if (extents.pt1.y > extents.pt2.y) offset.y = extents.pt1.y;
else offset.y = extents.pt2.y;
imageOffset.y = -getHeight();
extentsOffset.y = -pixelOffset;
}
if (isLeft) # isLeft half of view, get max x
{
if (extents.pt1.x > extents.pt2.x) offset.x = extents.pt1.x;
else offset.x = extents.pt2.x;
extentsOffset.x = pixelOffset;
}
else # isRight half of view, get min y
{
if (extents.pt1.x < extents.pt2.x) offset.x = extents.pt1.x;
else offset.x = extents.pt2.x;
imageOffset.x = -getWidth();
extentsOffset.x = -pixelOffset;
}
# Get screen pixels
offset = TransPoint2D(offset, ViewGetTransMapToView(view, vectorLayer.Projection));
offset = TransPoint2D(offset, ViewGetTransViewToScreen(view));
# Adjust by cursor position
offset = offset - cursor;
# Offset accounting for the size of the image
offset = offset + imageOffset;
# Offset from line extents
offset = offset + extentsOffset;
return offset;
}
# procedure to 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;
for i=0 to line.GetNumPoints()-1
{
obj = line.GetVertex(i);
map = ObjectToMap(lineVector, obj.x, obj.y, vecGeoref);
ret.AppendVertex(map);
}
return ret;
}
# Predefined function called when the cursor pauses triggering a datatip action event
func OnViewDataTipShowRequest(class GRE_VIEW view, class POINT2D point, class TOOLTIP datatip)
{
datatip.Delay = 500;
# Store the cursor position
local class POINT2D cursor = point;
# Get the cursor position in map coords
local class TRANSPARM screenToView = ViewGetTransViewToScreen(view, 1);
local class TRANSPARM viewToMap = ViewGetTransMapToView(view, vectorLayer.Projection, 1);
point = TransPoint2D(point, screenToView);
point = TransPoint2D(point, viewToMap);
# Translate 16 pixel distance to map projected distance
local class POINT2D tmppoint0;
tmppoint0.x = 0; tmppoint0.y = 0;
tmppoint0 = TransPoint2D(tmppoint0, screenToView);
tmppoint0 = TransPoint2D(tmppoint0, viewToMap);
local class POINT2D tmppoint;
tmppoint.x = sqrt(128); tmppoint.y = sqrt(128);
tmppoint = TransPoint2D(tmppoint, screenToView);
tmppoint = TransPoint2D(tmppoint, viewToMap);
local numeric dist = computeDistance(tmppoint0, tmppoint);
# Get the line from the cursor position
local numeric lineNum = FindClosestLine(lineVector, point.x, point.y, vecGeoref, dist);
if (lineNum == 0) return -1; # if we are not close enough, don't display graph
local class POLYLINE line = GetVectorLine(lineVector, lineNum);
line = convertObjectToMap(line);
# Highlight the line
vectorLayer.line.HighlightSingle(lineNum);
view.RedrawLayer(vectorLayer);
# Get the closest vertex for display
local numeric vertexNum = line.FindClosestVertex(point);
# Get the line to graph - x-dimension is distance, y is elevation
local class POLYLINE graphLine = constructGraphLine(line);
# Create the graphics context to draw the graph to
createGC();
# Draw the graph
drawGraph(graphLine, vertexNum);
# Compute Image tip offset and display
local class POINT2D offset;
offset = computeOffset(line, view, cursor);
datatip.SetImageTip(imagedev, maskdev, offset);
return 1;
}