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