// **********************************************************************
//
// <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/omGraphics/grid/SlopeGenerator.java,v $
// $RCSfile: SlopeGenerator.java,v $
// $Revision: 1.2.2.5 $
// $Date: 2005/12/22 18:42:47 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.omGraphics.grid;
import java.awt.Point;
import java.awt.Shape;
import com.bbn.openmap.LatLonPoint;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.OMGrid;
import com.bbn.openmap.omGraphics.OMRaster;
import com.bbn.openmap.omGraphics.SinkGraphic;
import com.bbn.openmap.proj.Length;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.Debug;
/**
* The SlopeGenerator is an OMGridGenerator that looks at elevation
* data and creates shading images from it. It currently works for
* short data only, since that what DTED elevation data is. Making it
* work with different data types is on the to-do list.
*/
public class SlopeGenerator implements OMGridGenerator {
protected int contrast = 5;
protected ElevationColors colors = new ColoredShadingColors();
public SlopeGenerator() {}
public SlopeGenerator(ElevationColors elevColors) {
setColors(elevColors);
}
public void setColors(ElevationColors elevColors) {
colors = elevColors;
}
public ElevationColors getColors() {
return colors;
}
public void setContrast(int val) {
if (val > 10 || val < 1) {
val = 5;
}
contrast = val;
}
public int getContrast() {
return contrast;
}
/**
* Called from within generate to create an OMRaster object for
* the OMGrid. This method exists to make it easier to extend this
* class to create an OMRaster as needed.
*/
protected OMRaster getRaster(OMGrid grid) {
return new OMRaster(grid.point1.x, grid.point1.y, grid.width, grid.height, new int[grid.width
* grid.height]);
}
protected boolean incomplete = false;
/**
* A method to check if the last image created was a complete one.
* If it was incomplete, then that means some pixels of the image
* were not set because they were outside of the projection.
*/
public boolean isIncompleteImage() {
return incomplete;
}
/**
* A more defining API method to get what this SlopeGenerator can
* create. An OMGridGenerator generates an OMGraphic, this method
* lets you specifically ask for an OMRaster if that's what you
* want. If SlopeGenerator is extended so that it doesn't
* necessarily return an OMRaster from generate, this method
* checks for that and will return null if generate returns
* something other than an OMRaster.
*
* @param grid
* @param proj
* @return OMRaster that reflects slope information.
*/
public OMRaster generateRasterForProjection(OMGrid grid, Projection proj) {
OMGraphic omg = generate(grid, proj);
if (omg instanceof OMRaster) {
return (OMRaster) omg;
} else {
return null;
}
}
/**
* Called from the OMGrid.generate() method to tell the generator
* to create something to represent the grid contents.
*/
public OMGraphic generate(OMGrid grid, Projection proj) {
Shape gridShape = grid.getShape();
// Don't generate the raster if the grid is off-map...
if (gridShape == null
|| !gridShape.intersects(0,
0,
proj.getWidth(),
proj.getHeight())) {
if (Debug.debugging("grid")) {
Debug.output("SlopeGenerator: OMGrid does not overlap map, skipping generation.");
}
return SinkGraphic.getSharedInstance();
}
OMRaster raster = getRaster(grid);
incomplete = false;
if (grid.height == 0 || grid.width == 0) {
Debug.message("grid", "SlopeGenerator: grid height/width ZERO!");
return raster;
}
GridData gd = grid.getData();
if (!(gd instanceof GridData.Short)) {
Debug.message("grid",
"SlopeGenerator: grid doesn't contain short data.");
return SinkGraphic.getSharedInstance();
}
int rows = grid.getRows();
int columns = grid.getColumns();
short[][] data = ((GridData.Short) gd).getData();
// boolean major = grid.getMajor();
double distance = getSlopeRun(grid, getContrast());
// Used for projections of image coordinates. Reused in the
// loops to save memory.
LatLonPoint llp = new LatLonPoint();
Point point = new Point();
ElevationColors colors = getColors();
if (colors == null) {
return SinkGraphic.getSharedInstance();
}
// x is the horizontal pixel being modified
for (short x = 0; x < grid.width; x++) {
// Check to make sure the pixels we're calculating are on
// the map.
int screenx = (int) grid.point1.getX() + x;
if (screenx < 0 || screenx > proj.getWidth()) {
incomplete = true;
continue;
}
for (short y = 0; y < grid.height; y++) {
// Check to make sure the pixels we're calculating are
// on the map.
int screeny = (int) grid.point1.getY() + y;
if (screeny < 0 || screeny > proj.getHeight()) {
incomplete = true;
continue;
}
// OK, on the map.
point.setLocation(screenx, screeny);
llp = proj.inverse(point.x, point.y, llp);
int yc = Math.round((llp.getLatitude() - grid.getLatitude())
/ grid.getVerticalResolution());
int xc = Math.round((llp.getLongitude() - grid.getLongitude())
/ grid.getHorizontalResolution());
// If the calculated index is out of the data ranges,
// push it to the end. Don't blow it off, it will
// cause unfilled data pixel lines to appear. You
// only want to blow off pixels that are not on the
// map, or are never going to be on the map.
if (yc < 0)
yc = 0;
if (yc > rows - 1)
yc = rows - 1;
if (xc < 0)
xc = 0;
if (xc > columns - 1)
xc = columns - 1;
int elevation = 0;
// Otherwise, get the elevation for the data point.
try {
elevation = (int) data[xc][yc];
} catch (ArrayIndexOutOfBoundsException aioobe) {
Debug.output("Error Accessing data array:\n\txc: " + xc
+ ", yc: " + yc + " for x: " + x + ", y: " + y);
}
// Slope shading calculations, get the upper left and
// lower right data points.
int xnw = xc - 1;
int xse = xc + 1;
// trying to smooth out the edge of the frame
if (xc == 0 || xnw < 0) {
xnw = xc;
}
if (xc == columns - 1 || xse > columns - 1) {
xse = columns - 1;
}
int yse = yc - 1;
int ynw = yc + 1;
// trying to smooth out the edge of the frame by
// handling the
// frame limits
if (yse < 0) {
yse = 0;
}
if (yc == rows - 1 || ynw > rows - 1) {
ynw = rows - 1;
}
// Get the elevation points for the slope measurement
// points.
try {
short e2 = data[xse][yse]; // down & right
// elevation
short e1 = data[xnw][ynw]; // up and left
// elevation
// colormap value darker for negative slopes,
// brighter for
// positive slopes.
// slope relative to nw sun
double slope = (e2 - e1) / distance;
raster.setPixel(x, y, colors.getARGB(elevation,
grid.getUnits(),
slope));
} catch (ArrayIndexOutOfBoundsException aioobe) {
Debug.output("Error Accessing data array:\n\txse: " + xse
+ ", yse: " + yse + "\n\txnw: " + xnw + ", ynw: "
+ ynw + "\n\tfor x: " + x + ", y: " + y);
}
}
}
raster.generate(proj);
if (Debug.debugging("grid"))
Debug.output("SlopeGenerator: leaving generate");
return raster;
}
public boolean needGenerateToRender() {
return true;
}
/**
* Method to calculate the run part of the slope (rise over run,
* right?). The rise is calcuated with the elevations of the
* points to the northwest and southeast of the point of concern,
* the run is some factor of the distance between those two posts.
*
* @param grid the OMGrid that contains the data, need to get
* units.
* @param contrastAdj contrast adjustment from 1-10, 5 being no
* adjustment. 10 is high contrast, 1 is low contrast.
*/
protected double getSlopeRun(OMGrid grid, int contrastAdj) {
double modifier = (double) .045;
for (int h = 0; h < contrastAdj; h++) {
modifier -= .005;
}
// Smaller modifiers result in more contrast. .025, by
// testing and judgement, looks like a good average, and the
// values have been set up to provide that with an adjustment
// of 5. 0 is very high contrast.
float vRes = grid.getVerticalResolution(); // Degrees per data
// point
float hRes = grid.getHorizontalResolution();
float vResRad = Length.DECIMAL_DEGREE.toRadians(vRes);
float hResRad = Length.DECIMAL_DEGREE.toRadians(hRes);
Length units = grid.getUnits();
double vDist = Math.pow(2.0 * (double) units.fromRadians(vResRad), 2);
double hDist = Math.pow(2.0 * (double) units.fromRadians(hResRad), 2);
return modifier * Math.sqrt(vDist + hDist);
}
}