#
# This is how to declare a global variable so that
# they can be used in functions before it's assigned.
# Without the explicit declaration, the variable will
# be assumed to be numeric or a Raster (depending on
# the case of the first letter).
#
class RVC_COLORMAP cmap;
class Color black, white;
class XmDrawingArea da;
class XmForm form; # The main form dialog
class GC gc; # The context for drawing into the view
raster Composite, BiomassRaster, FilteredBiomassRaster;
vector BiomassVector;
class GRE_VIEW view;
class GRE_GROUP group;
class GRE_LAYER_RASTER layer, overlay; # Our base layer
class GRE_LAYER_VECTOR voverlay;
class GRE_TOOL_POLYLINE tool;
class tooltip tip;
region reg;
array numeric histo[256];
numeric cellsize, unitconv;
numeric doingtest;
numeric haveBiomassRaster, haveBiomassVector;
class XmToggleButton drawhisto;
class PushButtonItem buttonConvertToVector, buttonFilterVector;
unitconv = GetUnitConvArea("square meters", "acres");
proc setTip () {
if (!haveBiomassRaster) {
if (tool.HasPosition) {
tip.string = "Press right button\nto close polygon";
}
else {
tip.string = "Press the left button\nand draw around a field.";
}
}
else {
tip.string = "";
}
}
#
# This is the callback function which will be called
# when button1 is pressed. All widget callbacks get
# two parameters (for now). The first is the widget
# generating the callback. The second is an optional
# "cbdata" and will be whatever was passed to
# WidgetAddCallback for the last parameter. It isn't
# really supported yet.
# When done, callbacks will also get a 3rd parameter
# which is the Callback Struct that the XmWidget passes
# There's currently no way to declare these things
#
proc cbQuit(class widget widget) {
DialogClose(form);
}
numeric sample_height = 15;
numeric sample_width = 40;
numeric sample_space = 5;
numeric sample_text_x = 37;
numeric num_ranges = 10;
string str$ = "";
#
# Draw the bar graph
#
proc drawGraph(class XmDrawingArea da) {
local numeric maxval, total, i, scale, w, y;
# Clear the drawing area
ActivateGC(gc);
SetColorRGB(0,0,0);
FillRect(0, 0, da.width, da.height);
maxval = 0;
total = 0;
for i = 1 to num_ranges {
maxval = SetMax(maxval, histo[i]);
total += histo[i];
}
if (total) { # Don't draw bars if nothing selected
w = da.width - 2 * sample_space;
scale = w / maxval;
if (drawhisto.set) {
for i = 1 to num_ranges {
y = (num_ranges - i + 1) * (sample_height + sample_space) + sample_space;
SetColor(ColorMapGetColor(cmap, i));
w = scale * histo[i];
if (w < 1 && histo[i] != 0) w = 1;
FillRect(sample_space, y, w, sample_height);
}
}
else {
DrawTextSetColors(white, black);
y = sample_space;
gc.TextStyle.JustifyRight = 0;
gc.TextStyle.JustifyCenter = 1;
DrawTextSimple("Acres", da.width / 2, y + sample_height - 4);
gc.TextStyle.JustifyRight = 1;
gc.TextStyle.JustifyCenter = 0;
DrawTextSetColors(black, white);
for i = 1 to num_ranges {
y = (num_ranges - i + 1) * (sample_height + sample_space) + sample_space;
SetColor(ColorMapGetColor(cmap, i));
FillRect(sample_space, y, sample_width, sample_height);
SetColorName("white");
DrawRect(sample_space, y, sample_width, sample_height);
str$ = sprintf("%.2f", histo[i] * cellsize * unitconv);
DrawTextSimple(str$, sample_text_x, y + sample_height - 4);
}
y = (num_ranges + 1) * (sample_height + sample_space) + sample_space;
str$ = sprintf("%.2f", total * cellsize * unitconv);
DrawTextSimple(str$, sample_text_x, y + sample_height - 4);
}
}
}
#
# Callback for when user clicks the right mouse button
# on the polygon tool (or hits apply)
#
proc cbToolApply(class GUI_GADGET_REGION tool) {
local numeric i, val, scale, minval, maxval, ir, red, total, offset, lo, hi;
reg = tool.Region;
FillRegion(RegionTrans(reg, ViewGetTransViewToScreen(view)));
reg = RegionTrans(reg, ViewGetTransLayerToView(view, layer, 1));
StatusSetDefaultHandle(view.StatusHandle);
ViewSetMessage(view, "Filling raster with 0");
BiomassRaster = 0;
if (1) {
ViewSetMessage(view, "Computing scale");
for i=0 to 200 histo[i] = 0;
for each Composite in reg.$Data {
ir = Composite.red;
red = Composite.green;
val = ((ir - red) / (ir + red) + 1) * 100;
histo[val] = histo[val] + 1;
}
total = 0;
for i=0 to 200 total += histo[i];
lo = total * .02;
hi = total * .98;
minval = -100;
maxval = -100;
total = 0;
for i = 0 to 200 {
if (total > lo && minval < -1) {
minval = (i / 100) - 1;
}
total += histo[i];
if (total > hi && maxval < -1) {
maxval = (i / 100) - 1;
}
}
scale = num_ranges / (maxval - minval);
offset = -minval;
printf("offset = %f, scale = %f\n", offset, scale);
}
else {
scale = num_ranges / 2.0;
offset = 1.0;
}
ViewSetMessage(view, "Processing region");
for i = 1 to num_ranges histo[i] = 0;
for each Composite in reg.$Data {
ir = Composite.red;
red = Composite.green;
val = (ir - red) / (ir + red);
val = Bound((val + offset) * scale + 1, 1, num_ranges);
histo[val] = histo[val] + 1;
BiomassRaster = val;
}
CloseRaster(BiomassRaster);
LayerShow(overlay, view);
ViewRedraw(view);
drawGraph(da);
# Do something with the region here
haveBiomassRaster = 1;
tool.HasPosition = 0; # Clears the current position
buttonConvertToVector.Disabled = 0;
setTip();
}
proc cbConvToVector () {
StatusSetDefaultHandle(view.StatusHandle);
if (0) { # Make this 1 to low-pass filter the raster first
FilteredBiomassRaster = 0;
ViewSetMessage(view, "Lowpass filter");
foreach BiomassRaster in reg.$Data {
FilteredBiomassRaster = FocalMedian(BiomassRaster, 3, 3);
}
ViewSetMessage(view, "Converting Raster to Vector");
BiomassVector = RasterToVectorBound(FilteredBiomassRaster);
}
else {
ViewSetMessage(view, "Converting Raster to Vector");
BiomassVector = RasterToVectorBound(BiomassRaster);
}
CloseVector(BiomassVector);
if (!voverlay) {
voverlay = GroupQuickAddVectorVar(group, BiomassVector);
voverlay.Poly.NormalStyle.BorderColor.name = "black";
voverlay.Line.NormalStyle.Color.name = "black";
}
LayerShow(voverlay, view);
ViewRedraw(view);
buttonFilterVector.Disabled = 0;
}
proc cbFilterVector () {
local numeric i, numPolys, numToDelete;
FilteredBiomassRaster = 0;
StatusSetDefaultHandle(view.StatusHandle);
ViewSetMessage(view, "Generating std stats table");
VectorToolkitInit(BiomassVector);
VectorUpdateStdAttributes(BiomassVector); # Compute std stats table
numToDelete = 0;
numPolys = NumVectorPolys(BiomassVector);
array numeric deleteList[numPolys];
ViewSetMessage(view, "Looking for polygons to delete");
for i=1 to numPolys {
if (BiomassVector.poly[i].POLYSTATS.Area < 40) {
numToDelete += 1;
deleteList[numToDelete] = i;
}
}
if (numToDelete) {
printf("Found %d polygons to delete\n", numToDelete);
ViewSetMessage(view, "Deleting tiny polygons");
# Now delete std attributes do delete polys doesn't have to maintain them
VectorSetFlags(BiomassVector, "NoDBStatTable");
VectorDeleteStdAttributes(BiomassVector);
# VectorDeletePolys(BiomassVector, deleteList, numToDelete, "LongestLine");
VectorDeletePolys(BiomassVector, deleteList, numToDelete);
}
ViewSetMessage(view, "Closing vector");
CloseVector(BiomassVector);
ViewRedraw(view);
}
proc cbHideOverlay(class widget widget) {
local numeric i;
LayerHide(overlay, view);
for i = 0 to 10 histo[i] = 0;
ViewRedraw(view);
drawGraph(da);
}
proc cbRedrawGraph() {
drawGraph(da);
}
proc cbToolPosSet(class GUI_GADGET tool) {
setTip();
}
#####################################################
#
# MAIN PROGRAM
#
# Get an input raster. We will use it's size to determine
# how big to make our window
GetInputRaster(Composite);
#
# Create the Biomass Raster that we'll use for the overlay
#
string outfilename$ = _context.ScriptDir + "\biomass.rvc";
if (fexists(outfilename$)) DeleteFile(outfilename$);
CreateRaster(BiomassRaster, outfilename$, "biomass", "Biomass output", NumLins(Composite), NumCols(Composite), "8-bit unsigned");
#CreateTempRaster(BiomassRaster, NumLins(Composite), NumCols(Composite), "8-bit unsigned");
#GetOutputRaster(BiomassRaster, NumLins(Composite), NumCols(Composite), "8-bit unsigned");
CopySubobjects(Composite, BiomassRaster, "georef");
cellsize = LinScale(Composite) * ColScale(Composite);
class COLOR c0 = ColorMapGetColor(cmap, 0);
c0.transp = 50; # Make it 50% transparent
ColorMapSetColorHIS(cmap, 10, 230, 50, 100); # Green
ColorMapSetColorHIS(cmap, 9, 180, 70, 100); # Yellow
ColorMapSetColorHIS(cmap, 8, 150, 70, 100); # Lt Orange
ColorMapSetColorHIS(cmap, 7, 144, 50, 100); # Dk Orange
ColorMapSetColorHIS(cmap, 6, 120, 50, 100); # Red
#ColorMapSetColorHIS(cmap, 5, 96, 50, 100);
ColorMapSetColorHIS(cmap, 5, 72, 50, 100); # Magenta
ColorMapSetColorHIS(cmap, 4, 48, 50, 100); # Purple
ColorMapSetColorHIS(cmap, 3, 10, 50, 100); # Blue
ColorMapSetColorHIS(cmap, 2, 342, 65, 100); # Lt Blue
ColorMapSetColorHIS(cmap, 1, 320, 70, 100); # Cyan
ColorMapSetColor(cmap, 0, c0);
ColorMapWriteToRastVar(BiomassRaster, cmap, "ColorMap");
SetNull(BiomassRaster, 0);
CreateTempRaster(FilteredBiomassRaster, NumLins(Composite), NumCols(Composite), "8-bit unsigned");
CopySubobjects(Composite, FilteredBiomassRaster, "georef");
#
# Create a temp vector for the biomass->vector conversion
#
#CreateTempVector(BiomassVector);
CreateVector(BiomassVector, outfilename$, "BiomassVect", "Biomass vector");
# Create a modal dialog (aka "window"). A modal dialog
# prevents other windows from accepting input until it
# goes away. This is the easiest kind to work with.
# Non-modal dialogs will be supported later
#
# This function returns a form that can be used as a
# parent for other widgets
#
# If we had another dialog which we wanted to use as
# a "parent" to this one, we would pass it as the
# 2nd parameter to CreateModalFormDialog().
form = CreateModalFormDialog("Relative Biomass Mapping");
#
# Create a push button on the form. Set its attachments
# so that sets in the lower left of the form. To attach
# an edge to the form rather than another widget, just
# set it to the form. In Motif, this would be done by
# setting the attachment to XmATTACH_FORM, which I plan
# to implement here too. Setting the RightPosition to 50
# ties the right edge of the button to a spot 50% of the
# width of the form, (assuming form.FractionBase still
# has the default value of 100) no matter how wide the
# form gets
#
class PUSHBUTTONITEM buttonClearOverlay = CreatePushButtonItem("Clear Overlay", cbHideOverlay);
class PUSHBUTTONITEM buttonConvertToVector = CreatePushButtonItem("Convert To Vector", cbConvToVector);
class PUSHBUTTONITEM buttonFilterVector = CreatePushButtonItem("Filter Vector", cbFilterVector);
class PUSHBUTTONITEM buttonExit = CreatePushButtonItem("Exit", cbQuit);
buttonConvertToVector.Disabled = 1;
buttonFilterVector.Disabled = 1;
class XmForm button_row = CreateButtonRow(form, buttonClearOverlay, buttonConvertToVector, buttonFilterVector, buttonExit);
button_row.BottomWidget = form;
drawhisto = CreateIconToggleButton(form, "RVCobjects", "histo", "Show as histogram");
drawhisto.LeftWidget = form;
drawhisto.TopWidget = form;
WidgetAddCallback(drawhisto.ValueChangedCallback, cbRedrawGraph);
class XmFrame frame = CreateFrame(form);
frame.TopWidget = drawhisto;
frame.BottomWidget = button_row;
frame.LeftWidget = form;
da = CreateDrawingArea(frame, 180, sample_width + 2 * sample_space + 2);
WidgetAddCallback(da.ExposeCallback, cbRedrawGraph);
#
# Create a 2D group to display the raster in and quick-add it.
#
group = DispCreate2DGroup();
layer = GroupQuickAddRasterVar(group, Composite);
overlay = GroupQuickAddRasterVar(group, BiomassRaster);
overlay.NullCellsTransparent = 0;
overlay.UsesTransparency = 1;
overlay.DoBlendMask = 1;
#
# Create a view for this group in our form. Default its size to
# the size of the raster. For a real program, you'd want to
# limit this to something reasonable.
#
numeric height = NumLins(Composite);
numeric width = NumCols(Composite);
while (height > 400 or width > 640) {
height /= 2;
width /= 2;
}
view = GroupCreateView(group, form, "view", height, width);
#
# Make widget attachments for the view. Connect it to the form
# on all edges except the bottom. There, attach it to one of the
# buttons.
# Some notes about making attachment:
# 1) If you attach widget A to Widget B, don't attach B to A
# or anything else which would cause a circular reference.
# 2) The widget created and attached last is what will resize
# with the window. If I had created the view first and then
# the buttons, resizing the window would make the buttons
# bigger.
form.BottomWidget = button_row;
LayerHide(overlay, view);
view.ShowDataTips = 0;
# Hide the whole icon bar to show that we can do it.
# (commented out because I don't really want it hidden)
#view.IconBar.hidden = 1;
#
# Hide the scale position. Do this BEFORE adding the
# EndDrawCallback below. Turning off the ScalePos row
# Will cause a redraw. If the callback function tries
# to draw onto a drawing area that doesn't exist yet,
# it will IllOp. (Yes, now you too can program your
# own IllOps!)
#
#view.ScalePosVisible = 0;
#
# Add a callback to the view's DrawEndCallback.
# This will be called when the drawing is done.
#
# Before I'm done, I plan to merge WidgetAddCallback()
# and DispAddCallback() into a single AddCallback().
# This will be done by making all callback lists a
# subclass of a single class.
#
DispAddCallback(view.RestoreCallback, cbRedrawGraph);
#
# Create a region tool on the view
#
tool = ViewCreatePolygonTool(view, "My Tool", "edit_query", "standard");
ToolAddCallback(tool.ActivateCallback, cbToolApply);
ToolAddCallback(tool.PositionSetCallback, cbToolPosSet);
ToolAddCallback(tool.PositionClearedCallback, cbToolPosSet);
#
# Have to call this after adding any tools of our own
#
#ViewAddToolIcons(view);
ViewAddStandardTools(view);
#
# Open the dialog Need to do this before creating the GC
# because until then the drawing area has no window.
# Would like a way around this. Could my CreateGC
# realize it first if necessary?
#
DialogOpen(form);
#
# Create a GC (Graphics Context) for the drawing area
# You can have multiple gc's per drawing area. Drawing
# functions will always use the active GC.
#
gc = CreateGCForDrawingArea(da);
gc.TextStyle.JustifyRight = 1;
# Clear the drawing area
ActivateGC(gc);
SetColorRGB(0,0,0);
black.name = "black";
white.name = "white";
FillRect(0, 0, da.width, da.height);
DrawTextSetFont("stork.of");
DrawTextSetColors(black, white);
DrawTextSetHeightPixels(9);
ViewRedraw(view);
tool.Managed = 1;
ViewSetMessage(view, "Draw around a field and press \"Apply\"");
tip = CreateToolTip(view.DrawingArea, "Draw around a field\nand press \"Apply\"");
tip.Disabled = 0;
tip.Delay = 5000; # 5 seconds (5000 ms)
setTip();
#StatusSetDefaultHandle(view.StatusHandle);
#
# Wait for the dialog to close. This function will
# not return until the dialog closes.
# The normal way to do this is have a button or menu
# item who's callback calls DialogClose(form).
# Clicking on the "X" button on the title bar will
# close it (although I plan to let you override that
# too)
#
DialogWaitForClose(form);
#
# Destroy our form to free up its memory. Destroying the
# main form will also destroy all its children.
# Need to destroy the view before the form because
# otherwise the tool gets destroyed before the view
# and the view tries to disable a nonexistant tool.
# Result: IllOP!
#
ViewDestroy(view);
GroupDestroy(group);
DestroyWidget(form);