// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/etopo/ETOPOLayer.java,v $
// $RCSfile: ETOPOLayer.java,v $
// $Revision: 1.5.2.4 $
// $Date: 2008/02/27 01:53:54 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.layer.etopo;
/* Java Core */
import java.awt.Component;
import java.awt.Point;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileNotFoundException;
import java.io.IOException;
/* OpenMap */
import com.bbn.openmap.LatLonPoint;
import com.bbn.openmap.io.BinaryBufferedFile;
import com.bbn.openmap.io.FormatException;
import com.bbn.openmap.layer.OMGraphicHandlerLayer;
import com.bbn.openmap.layer.policy.ListResetPCPolicy;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMRaster;
import com.bbn.openmap.proj.CADRG;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PaletteHelper;
import com.bbn.openmap.util.PropUtils;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
/**
* ETOPOLayer extends Layer to provide rendering of the ETOPO5 world elevation
* data set. The ETOPO5 data consists of terrain altitude and ocean depth
* measurements at 5 minute intervals for the entire globe. Rendering is allowed
* in any projection that implements the inverse(int,int) method. Two types of
* rendering are provided: grayscale slope-shaded and colored slope-shaded.
* <p>
* The distribution consists of the following:
* <ul>
* <li>1. ETOPOLayer.java</li>
* <li>2. ETOPO5 (5 minute spacing data set, 4320x2160 shorts, ~18MB) </li>
* <li>3. ETOPO10 (10 minute spacing data set, sampled from ETOPO5, ~4.6MB)</li>
* <li>4. ETOPO15 (15 minute spacing data set, sampled from ETOPO5, ~2MB)</li>
* <li>5. ETOPOLayer.properties (example properties for openmap.properties)</li>
* </ul>
* <p>
* The sampled ETOPO data sets are provided to speed up the loading of data to
* compute the slope shading. The algorithm inverse projects the x/y screen
* coords (for the entire projection screen space) to get the corresponding
* lat/lon coords then samples the database to get altitude/depth and slope
* values. While this method is slower than the forward projection method, it
* does provide a more attractive screen presentation and will support all
* projections (not just the equidistant cylindrical). A palette provides the
* ability to choose between the 5,10, or 15 minute resolutions, as well as
* color or grayscale selection, transparency, and slope contrast.
* <p>
* The ETOPOLayer also relies on properties to set its variables, such as the
* etopo frame paths (there can be several at a time), the opaqueness of the
* frame images, number of colors to use, and some other display variables. The
* ETOPOLayer properties look something like this:
* <P>
*
* #------------------------------ <BR># Properties for ETOPOLayer <BR>
* #------------------------------ <BR># This property should reflect the paths
* to the etopo directory <BR>
* etopo.path=c:/openmap/share <BR>
* <BR># Number between 0-255: 0 is transparent, 255 is opaque <BR>
* etopo.opaque=255 <BR>
* <BR># Number of colors to use on the maps - 16, 32, 216 <BR>
* etopo.number.colors=216 <BR>
* <BR># Type of display for the data <BR># 0 = grayscale slope shading <BR>#
* 1 = colored slope shading <BR>
* etopo.view.type=1 <BR>
* <BR># Contrast setting, 1-5 <BR>
* etopo.contrast=3 <BR>
* <BR># lat/lon spacing in minutes <BR># must be 5, 10, or 15 <BR>
* etopo.minute.spacing=10 <BR>
* <BR>
* #------------------------------------- <BR># End of properties for
* ETOPOLayer <BR>
* #------------------------------------- <BR>
*
*/
public class ETOPOLayer extends OMGraphicHandlerLayer implements ActionListener {
/** Gray scale slope shading, sun from the Northwest. */
public static final int SLOPESHADING = 0;
/**
* Colorized slope shading. Color basnds are based on elevation, and are
* accented by shaded indications.
*/
public static final int COLOREDSHADING = 1;
/** Default contrast setting for slope shading. */
public static final int DEFAULT_SLOPE_ADJUST = 3;
/** Default minute spacing */
public static final int DEFAULT_MINUTE_SPACING = 10;
/** for colorizing */
public final static int DEFAULT_OPAQUENESS = 255;
/**
* The paths to the ETOPO directory, telling where the data is.
*/
protected String path;
/** The etopo elevation data */
protected short[] dataBuffer = null;
protected int bufferWidth;
protected int bufferHeight;
/** The current resolution (in minutes) */
protected int minuteSpacing;
/** ETOPO elevation files */
protected final static String[] etopoFileNames = { "/ETOPO2", "/ETOPO5",
"/ETOPO10", "/ETOPO15" }; // ep-g
/** dimensions of the ETOPO files (don't mess with these!) */
protected final static int[] etopoWidths = { 10800, 4320, 2160, 1440 };// ep-g
protected final static int[] etopoHeights = { 5400, 2160, 1080, 720 }; // ep-g
/**
* Spacings (in meters) between adjacent lon points at the equater. The
* values here were aesthetically defined (they are not the actual spacings)
*/
protected final static double[] etopoSpacings = { 1800., 3500., 7000.,
10500. }; // ep-g
/**
* The display type for the etopo images. Slope shading is grayscale terrain
* modeling with highlights and shading, with the 'sun' being in the
* NorthWest. Colored Elevation shading is the same thing, except colors are
* added to indicate the elevation. Band shading colors the pixels according
* to a range of elevations.
*/
protected int viewType;
/** The elevation range to use for each color in band shading. */
protected int bandHeight;
/** A contrast adjustment, for slope shading (1-5). */
protected int slopeAdjust;
/** transparency control */
protected int opaqueness;
/**
* Number of pixel spacers that should be added to a data file, per line, to
* adjust for skewing.
*/
protected int spacer = 0;
/** property suffixes */
public static final String ETOPOPathProperty = "path";
public static final String OpaquenessProperty = "opaque";
public static final String ETOPOViewTypeProperty = "view.type";
public static final String ETOPOSlopeAdjustProperty = "contrast";
public static final String ETOPOMinuteSpacingProperty = "minute.spacing";
public static final String ETOPOPixelSpacerProperty = "spacer";
/**
* Holds the slope values, updated when the resolution changes or the slope
* adjustment (contrast) is changed. Slope values are scaled between -127 to
* 127.
*/
protected byte[] slopeMap = null;
/** elevation bands */
protected static final int[] elevLimit = { -11000, -9000, -7000, -5000,
-3000, -1500, 0, 250, 500, 750, 1000, 2000, 3500, 5000 };
/** number of elevation bands */
protected static final int elevLimitCnt = 14;
/** elevation band colors (one for each elevation band) */
protected static final int[] redElev = { 0, 0, 4, 20, 124, 130, 135, 117,
252, 253, 229, 244, 252, 132 };
protected static final int[] greenElev = { 2, 12, 51, 159, 235, 255, 235,
255, 236, 162, 115, 50, 20, 132 };
protected static final int[] blueElev = { 76, 145, 242, 249, 252, 255, 110,
58, 29, 35, 5, 14, 46, 132 };
/** for slope shading colors, indexed by elevation band then slope */
protected static Color[][] slopeColors = null;
/* flag to recompute slope map */
protected boolean slopeReset = true;
/* flag to load new elevation file */
protected boolean spacingReset = true;
/**
* The default constructor for the Layer. All of the attributes are set to
* their default values.
*/
public ETOPOLayer() {
this(null);
}
/**
* The default constructor for the Layer. All of the attributes are set to
* their default values.
*
* @param pathToETOPODir path to the directory holding the ETOPO data
*/
public ETOPOLayer(String pathToETOPODir) {
setName("ETOPO");
setDefaultValues();
path = pathToETOPODir;
setProjectionChangePolicy(new ListResetPCPolicy(this));
}
public void setPath(String pathToETOPODir) {
path = pathToETOPODir;
}
protected void setDefaultValues() {
// defaults
path = null;
dataBuffer = null;
opaqueness = DEFAULT_OPAQUENESS;
slopeAdjust = DEFAULT_SLOPE_ADJUST;
viewType = COLOREDSHADING;
minuteSpacing = DEFAULT_MINUTE_SPACING;
}
/* returns the color lookup index based on elevation */
protected int getElevIndex(short el) {
for (int i = 0; i < elevLimitCnt - 1; i++)
if (el < elevLimit[i + 1])
return i;
return elevLimitCnt - 1;
}
/* returns a color based on slope and elevation */
protected Color getColor(short elevation, byte slopeVal) {
// build first time
if (slopeColors == null) {
// allocate storage for elevation bands, 8 slope bands
slopeColors = new Color[elevLimitCnt][8];
// process each elevation band
for (int i = 0; i < elevLimitCnt; i++) {
// get base color (0 slope color)
Color base = new Color(redElev[i], greenElev[i], blueElev[i]);
// call the "brighter" method on the base color for
// positive slope
for (int j = 4; j < 8; j++) {
// set
if (j == 4)
slopeColors[i][j] = base;
else
slopeColors[i][j] = slopeColors[i][j - 1].brighter();
}
// call the "darker" method on the base color for
// negative slopes
for (int k = 3; k >= 0; k--) {
// set
slopeColors[i][k] = slopeColors[i][k + 1].darker();
}
}
}
// get the elevation band index
int elIdx = getElevIndex(elevation);
// compute slope idx
int slopeIdx = ((int) slopeVal + 127) / 32;
// return color
Color norm = slopeColors[elIdx][slopeIdx];
// set alpha
return new Color(norm.getRed(), norm.getGreen(), norm.getBlue(), opaqueness);
}
/**
* Set all the ETOPO properties from a properties object.
*/
public void setProperties(String prefix, java.util.Properties properties) {
super.setProperties(prefix, properties);
prefix = PropUtils.getScopedPropertyPrefix(this);
path = properties.getProperty(prefix + ETOPOPathProperty);
opaqueness = PropUtils.intFromProperties(properties, prefix
+ OpaquenessProperty, DEFAULT_OPAQUENESS);
viewType = PropUtils.intFromProperties(properties, prefix
+ ETOPOViewTypeProperty, COLOREDSHADING);
slopeAdjust = PropUtils.intFromProperties(properties, prefix
+ ETOPOSlopeAdjustProperty, DEFAULT_SLOPE_ADJUST);
minuteSpacing = PropUtils.intFromProperties(properties, prefix
+ ETOPOMinuteSpacingProperty, DEFAULT_MINUTE_SPACING);
spacer = PropUtils.intFromProperties(properties, prefix
+ ETOPOPixelSpacerProperty, spacer);
}
/**
* Builds the slope index map. This method is called when the ETOPO
* resolution changes and when the slope contrast changes. The slope of the
* terrain is cliped; slopes are between the range of +/- 45 deg. The
* calculated slope value is then linearly scaled to the range +/- 127.
*/
protected void buildSlopeMap() {
// this should never happen, but...
if (dataBuffer == null)
return;
// get resolution index
int resIdx = minuteSpacing / 5; // ep-g
if (resIdx < 0)
resIdx = 0;
else if (resIdx > 3) // ep-g
resIdx = 3; // ep-g
// Set deltaX constant. The deltaX is actually is smaller at
// latitude
// extremes, but
double deltaX = etopoSpacings[resIdx];
// allocate storage for slope map
slopeMap = new byte[bufferWidth * bufferHeight];
// process dataBuffer to create slope
for (int y = 0; y < bufferHeight; y++) {
// compute the lattitude of this
double lat = 90. - 180. * (double) y / (double) bufferHeight;
// get cosine of the latitude. This is used because the
// spacing between minutes gets smaller in high latitude
// extremes.
double coslat = Math.cos(Math.toRadians(lat));
// for scaling the slope
double slopeScaler = (double) slopeAdjust * coslat / deltaX;
// indexcies
int idx0 = y * bufferWidth;
// do each row
for (int x = 0; x < bufferWidth; x++) {
// indexcies
int idx1 = idx0 + x;
int idx2 = idx1 + bufferWidth;
;
// special case at end
if (y == bufferHeight - 1)
idx2 = idx1;
// get altitudes
double d1 = (double) dataBuffer[idx1];
double d2 = (double) dataBuffer[idx2];
// compute (lookup) slope
double slope = slopeScaler * (d2 - d1);
// clip
if (slope > 0.99)
slope = 0.99;
else if (slope < -0.99)
slope = -0.99;
// scale
int islope = (int) (slope * 127.);
// store
slopeMap[idx1] = (byte) islope;
}
}
}
/**
* Loads the database from the appropriate file based on the current
* resolution. The data files are in INTEL format (must call
* BinaryBufferedFile.byteOrder(true)).
*/
protected void loadBuffer() {
// get the resolution index
int resIdx = minuteSpacing / 5; // ep-g
if (resIdx < 0)
resIdx = 0;
else if (resIdx > 3) // ep-g
resIdx = 3; // ep-g
// build file name
String fileName = path + etopoFileNames[resIdx];
// Clean this out...dfd
dataBuffer = null;
try {
// treat as buffered binary
BinaryBufferedFile binFile = new BinaryBufferedFile(fileName);
binFile.byteOrder(true);
// set width/height
bufferWidth = etopoWidths[resIdx];
bufferHeight = etopoHeights[resIdx];
int spacer = 1;
// don't know why I have to do this, but there seems to be
// a wrapping thing going on with different data sets.
switch (minuteSpacing) {
case (2):
spacer = 1 + this.spacer;
break;
case (5):
spacer = 0 + this.spacer;
break;
default:
spacer = 1 + this.spacer;
}
// allocate storage
dataBuffer = new short[(bufferWidth + spacer) * bufferHeight];
// read data
for (int i = 0; i < bufferWidth * bufferHeight; i++)
dataBuffer[i] = binFile.readShort();
// done
binFile.close();
// This is important for image creation.
bufferWidth += spacer;
} catch (FileNotFoundException e) {
Debug.error("ETOPOLayer loadBuffer(): file " + fileName
+ " not found");
} catch (IOException e) {
Debug.error("ETOPOLayer loadBuffer(): File IO Error!\n"
+ e.toString());
} catch (FormatException e) {
Debug.error("ETOPOLayer loadBuffer(): Format exception!\n"
+ e.toString());
}
}
/*
* Builds the raster image that has the dimensions of the current
* projection. The alogorithm is is follows: <P><pre> allocate storage the
* size of the projection (use ints for RGBA)
*
* for each screen point
*
* inverse project screen point to get lat/lon (world coords) get altitude
* and/or slope at the world coord compute (lookup) color at the world coord
* set color value into screen coord location
*
* end
*
* create OMRaster from the int array data. </pre>
*
* The code contains a HACK (primarily for the Orthographic projection)
* since * x/y values which would return an "Outer Space" value actually
* return lat/lon values for the center of the projection (see
* Orthographic.inverse(...)). This resulted in the "Outer Space" being
* painted the color of whatever the center lat/lon was. The HACK turns any
* center lat/lon value into black. Of course, this causes valid center
* lat/lon values to be painted black, but the trade off is worth it
* visually. The appropriate method may be to have Projection.inverse and
* its variants raise an exception for "Outer Space" values.
*/
protected OMRaster buildRaster() {
// initialize the return
OMRaster ret = null;
Projection projection = getProjection();
// work with the slopeMap
if (slopeMap != null) {
// compute our deltas
int width = projection.getWidth();
int height = projection.getHeight();
// create int array to hold colors
int[] colors = new int[width * height];
// compute scalers for lat/lon indicies
float scy = (float) bufferHeight / 180F;
float scx = (float) bufferWidth / 360F;
// starting and ending indices
int sx = 0, sy = 0, ex = width, ey = height;
// handle CADRG
if (projection instanceof CADRG) {
// get corners
LatLonPoint ul = projection.getUpperLeft();
LatLonPoint lr = projection.getLowerRight();
// set start/end indicies
Point ulp = projection.forward(ul);
Point lrp = projection.forward(lr);
sx = (int) ulp.getX();
ex = (int) lrp.getX();
sy = (int) ulp.getY();
ey = (int) lrp.getY();
}
// get the center lat/lon (used by the HACK, see above in
// method description)
LatLonPoint center = projection.getCenter();
LatLonPoint llp = new LatLonPoint();
// build array
for (int y = sy; y < ey; y++) {
// process each column
for (int x = sx; x < ex; x++) {
// inverse project x,y to lon,lat
projection.inverse(x, y, llp);
// get point values
float lat = llp.getLatitude();
float lon = llp.getLongitude();
// check... dfd
if (minuteSpacing == 2) {
lon += 180.;
} else {
if (lon < 0.)
lon += 360.;
}
// find indicies
int lat_idx = (int) ((90. - lat) * scy);
int lon_idx = (int) (lon * scx);
// offset
int ofs = lon_idx + lat_idx * bufferWidth;
// make a color
int idx = 0;
int gray = 0;
try {
// get elevation
short el = dataBuffer[ofs];
// slope
byte sl = slopeMap[ofs];
// our index
idx = y * width + x;
// create a color
Color pix = null;
if (viewType == SLOPESHADING) {
// HACK (see method description above)
if ((llp.getLatitude() == center.getLatitude())
&& (llp.getLongitude() == center.getLongitude()))
gray = 0;
else
gray = 127 + sl;
pix = new Color(gray, gray, gray, opaqueness);
} else if (viewType == COLOREDSHADING) {
// HACK (see method description above)
if ((llp.getLatitude() == center.getLatitude())
&& (llp.getLongitude() == center.getLongitude()))
pix = new Color(0, 0, 0, opaqueness);
else
pix = getColor(el, sl);
}
// set
colors[idx] = pix.getRGB();
}
// tried to set a bad color level
catch (IllegalArgumentException e) {
Debug.error(e.toString() + ":" + gray);
}
// bad index
catch (ArrayIndexOutOfBoundsException e) {
Debug.error(e.toString() + ":" + idx);
}
}
}
// create the raster
ret = new OMRaster(0, 0, width, height, colors);
}
// return or raster
return ret;
}
/**
* Prepares the graphics for the layer. This is where the getRectangle()
* method call is made on the etopo.
* <p>
* Occasionally it is necessary to abort a prepare call. When this happens,
* the map will set the cancel bit in the LayerThread, (the thread that is
* running the prepare). If this Layer needs to do any cleanups during the
* abort, it should do so, but return out of the prepare asap.
*/
public synchronized OMGraphicList prepare() {
if (isCancelled()) {
Debug.message("etopo", getName()
+ "|ETOPOLayer.prepare(): aborted.");
return null;
}
Projection projection = getProjection();
if (projection == null) {
Debug.error("ETOPO Layer needs to be added to the MapBean before it can draw images!");
return new OMGraphicList();
}
// load the buffer
if (dataBuffer == null || spacingReset) {
loadBuffer();
spacingReset = false;
slopeReset = true;
}
// re-do the slope map
if (slopeReset) {
buildSlopeMap();
slopeReset = false;
}
Debug.message("basic", getName() + "|ETOPOLayer.prepare(): doing it");
// Setting the OMGraphicsList for this layer. Remember, the
// OMGraphicList is made up of OMGraphics, which are generated
// (projected) when the graphics are added to the list. So,
// after this call, the list is ready for painting.
// call getRectangle();
if (Debug.debugging("etopo")) {
Debug.output(getName() + "|ETOPOLayer.prepare(): "
+ "calling getRectangle " + " with projection: "
+ projection + " ul = " + projection.getUpperLeft()
+ " lr = " + projection.getLowerRight());
}
// build graphics list
OMGraphicList omGraphicList = new OMGraphicList();
omGraphicList.addOMGraphic(buildRaster());
// ///////////////////
// safe quit
int size = 0;
if (omGraphicList != null) {
size = omGraphicList.size();
Debug.message("basic", getName()
+ "|ETOPOLayer.prepare(): finished with " + size
+ " graphics");
} else {
Debug.message("basic", getName()
+ "|ETOPOLayer.prepare(): finished with null graphics list");
omGraphicList = new OMGraphicList();
}
// Don't forget to project them. Since they are only being
// recalled if the projection hase changed, then we need to
// force a reprojection of all of them because the screen
// position has changed.
omGraphicList.project(projection, true);
return omGraphicList;
}
// ----------------------------------------------------------------------
// GUI
// ----------------------------------------------------------------------
/** The user interface palette for the ETOPO layer. */
protected Box palette = null;
/** Creates the interface palette. */
public Component getGUI() {
if (palette == null) {
if (Debug.debugging("etopo"))
Debug.output("ETOPOLayer: creating ETOPO Palette.");
palette = Box.createVerticalBox();
Box subbox0 = Box.createHorizontalBox();
Box subbox1 = Box.createHorizontalBox();
Box subbox2 = Box.createVerticalBox();
Box subbox3 = Box.createHorizontalBox();
// The ETOPO resolution selector
JPanel resPanel = PaletteHelper.createPaletteJPanel("Lat/Lon Spacing");
String[] resStrings = { "2 Minute", "5 Minute", "10 Minute",
"15 Minute" }; // ep-g
JComboBox resList = new JComboBox(resStrings);
resList.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComboBox jcb = (JComboBox) e.getSource();
int newRes = jcb.getSelectedIndex();
int curRes = minuteSpacing / 5; // ep-g
if (curRes != newRes)
spacingReset = true;
switch (newRes) {
case 0:
minuteSpacing = 2;
break; // ep-g
case 1:
minuteSpacing = 5;
break; // ep-g
case 2:
minuteSpacing = 10;
break; // ep-g
case 3:
minuteSpacing = 15;
break; // ep-g
}
}
});
resList.setSelectedIndex(minuteSpacing / 5); // ep-g
resPanel.add(resList);
// The ETOPO view selector
JPanel viewPanel = PaletteHelper.createPaletteJPanel("View Type");
String[] viewStrings = { "Grayscale Shading", "Color Shading" };
JComboBox viewList = new JComboBox(viewStrings);
viewList.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComboBox jcb = (JComboBox) e.getSource();
int newView = jcb.getSelectedIndex();
if (newView != viewType)
slopeReset = true;
switch (newView) {
case 0:
viewType = SLOPESHADING;
break;
case 1:
viewType = COLOREDSHADING;
break;
}
}
});
viewList.setSelectedIndex(viewType);
viewPanel.add(viewList);
// The ETOPO Contrast Adjuster
JPanel contrastPanel = PaletteHelper.createPaletteJPanel("Contrast Adjustment");
JSlider contrastSlide = new JSlider(JSlider.HORIZONTAL, 1/* min */, 5/* max */, 3/* inital */);
java.util.Hashtable dict = new java.util.Hashtable();
dict.put(new Integer(1), new JLabel("min"));
dict.put(new Integer(5), new JLabel("max"));
contrastSlide.setLabelTable(dict);
contrastSlide.setPaintLabels(true);
contrastSlide.setMajorTickSpacing(1);
contrastSlide.setPaintTicks(true);
contrastSlide.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent ce) {
JSlider slider = (JSlider) ce.getSource();
if (slider.getValueIsAdjusting()) {
Debug.output("ETOPOLayer - Contrast Slider value = "
+ slider.getValue());
slopeAdjust = slider.getValue();
}
}
});
contrastPanel.add(contrastSlide);
// The ETOPO Opaqueness
JPanel opaquenessPanel = PaletteHelper.createPaletteJPanel("Opaqueness");
JSlider opaquenessSlide = new JSlider(JSlider.HORIZONTAL, 0/* min */, 255/* max */, opaqueness/* inital */);
opaquenessSlide.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent ce) {
JSlider slider = (JSlider) ce.getSource();
if (slider.getValueIsAdjusting()) {
fireRequestInfoLine("ETOPOLayer - Opaqueness Slider value = "
+ slider.getValue());
opaqueness = slider.getValue();
}
}
});
opaquenessPanel.add(opaquenessSlide);
JButton redraw = new JButton("Redraw ETOPO Layer");
redraw.addActionListener(this);
redraw.setActionCommand(RedrawCmd);
subbox0.add(resPanel);
palette.add(subbox0);
subbox1.add(viewPanel);
palette.add(subbox1);
subbox2.add(contrastPanel);
subbox2.add(opaquenessPanel);
palette.add(subbox2);
subbox3.add(redraw);
palette.add(subbox3);
}
return palette;
}
// ----------------------------------------------------------------------
// ActionListener interface implementation
// ----------------------------------------------------------------------
/**
* Used just for the redraw button.
*/
public void actionPerformed(ActionEvent e) {
super.actionPerformed(e);
if (e.getActionCommand() == RedrawCmd) {
doPrepare();
}
}
}