LineProfileGraphTip.sml

  Download

More scripts: Enhanced Data Tip

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;
}