package edu.oregonstate.cartography.grid;
import static edu.oregonstate.cartography.grid.Model.ForegroundVisualization.ILLUMINATED_CONTOURS;
import edu.oregonstate.cartography.grid.operators.ColorizerOperator;
import edu.oregonstate.cartography.grid.operators.ColorizerOperator.ColorVisualization;
import edu.oregonstate.cartography.grid.operators.GridAddOperator;
import edu.oregonstate.cartography.grid.operators.GridCopyOperator;
import edu.oregonstate.cartography.grid.operators.GridMaskOperator;
import edu.oregonstate.cartography.grid.operators.GridScaleOperator;
import edu.oregonstate.cartography.grid.operators.GridScaleToRangeOperator;
import edu.oregonstate.cartography.grid.operators.GridSlopeOperator;
import edu.oregonstate.cartography.grid.operators.GridVoidOperator;
import edu.oregonstate.cartography.grid.operators.IlluminatedContoursOperator;
import edu.oregonstate.cartography.grid.operators.PlanObliqueOperator;
import edu.oregonstate.cartography.gui.bivariate.BivariateColorRenderer;
import edu.oregonstate.cartography.gui.ProgressIndicator;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import edu.oregonstate.cartography.gui.bivariate.ColorLUTInterface;
/**
*
* @author Bernhard Jenny, Cartography and Geovisualization Group, Oregon State
* University
*/
public class Model implements Cloneable {
public class ColorRamp {
/**
*
* @param name
* @param colors
* @param colorPositions
*/
public ColorRamp(String name, Color[] colors, float[] colorPositions) {
this.name = name;
this.colors = colors;
this.colorPositions = colorPositions;
}
/**
* Copy constructor
* @param cr ColorRamp to copy
*/
public ColorRamp(ColorRamp cr) {
this.name = cr.name;
this.colors = new Color[cr.colors.length];
this.colorPositions = new float[cr.colorPositions.length];
System.arraycopy( cr.colorPositions, 0, colorPositions, 0, colorPositions.length);
for (int i = 0; i < colors.length; i++) {
colors[i] = new Color(cr.colors[i].getRGB());
}
}
/**
* Name of color ramp
*/
public String name;
/**
* color definitions
*/
public Color[] colors;
/**
* relative positions between 0 and 1
*/
public float[] colorPositions;
}
public final ArrayList<ColorRamp> predefinedColorRamps;
public enum ForegroundVisualization {
NONE, ILLUMINATED_CONTOURS, SHADED_CONTOURS
}
/**
* original grid
*/
private Grid grid;
/**
* minimum and maximum value of original grid
*/
private float[] gridMinMax;
/**
* Laplacian pyramid of original grid
*/
public LaplacianPyramid laplacianPyramid;
/**
* modified grid composed of summed pyramids
*/
private Grid generalizedGrid;
/**
* slope values of generalized grid.
*/
private Grid generalizedSlopeGrid;
/**
* the number of levels of the Laplacian pyramid that are filtered
*/
public int generalizationMaxLevels = 2;
/**
* amount of filtering applied to generalizationMaxLevels pyramid levels.
* Between -1 and +1. With -1 the weight for all generalizationMaxLevels is
* 1 (hence no generalization). With +1 the weigh for all
* generalizationMaxLevels is 0.
*/
private double generalizationDetails = -1;
/**
* illumination azimuth
*/
public int azimuth = 315;
/**
* illumination zenith
*/
public int zenith = 45;
/**
* ambient illumination component. Usually between -0.5 and +0.5
*/
public double ambientLight = 0;
/**
* vertical exaggeration applied when shading
*/
public float shadingVerticalExaggeration = 1;
/**
* terrain coloring in the background (shading, hypsometric tints, etc)
*/
public ColorVisualization backgroundVisualization = ColorVisualization.GRAY_SHADING;
/**
* terrain visualization in the foreground (contours)
*/
public ForegroundVisualization foregroundVisualization = ForegroundVisualization.NONE;
/**
* color definitions with relative elevation values
*/
public ColorRamp colorRamp;
/**
* color applied when the entire background is filled with a single color
*/
public Color solidColor = Color.LIGHT_GRAY;
/**
* color for illuminated contour lines. Default is white
*/
public int contoursIlluminatedColor = 0x00FFFFFF;
/**
* color for shaded contour lines. Default is black.
*/
public int contoursShadowedColor = 0x00000000;
/**
* contour interval
*/
public double contoursInterval = 200;
/**
* line width of illuminated contours (relative to cell size) at lowest
* elevation
*/
public double contoursIlluminatedWidthLow = 1;
/**
* line width of illuminated contours (relative to cell size) at highest
* elevation
*/
public double contoursIlluminatedWidthHigh = 1;
/**
* line width of shaded contours (relative to cell size) at lowest elevation
*/
public double contoursShadowWidthLow = 1;
/**
* line width of shaded contours (relative to cell size) at highest
* elevation
*/
public double contoursShadowWidthHigh = 1;
/**
* contour line widths are never smaller than this value (relative to cell
* size)
*/
public double contoursMinWidth = 0.2;
/**
* minimum distance between contour lines (relative to cell size)
*/
public double contoursMinDist = 0;
/**
* contour gray values are smoothly interpolated between illuminated and
* shaded slope. This angle defines the range of interpolation.
*/
public int contoursGradientAngle = 0;
/**
* standard deviation of Gaussian blur filter to despeckle contour lines
*/
public double contoursAspectGaussBlur;
/**
* transition angle between illuminated and shaded contour lines
*/
public int contoursTransitionAngle = 90;
/**
* inclination angle for plan oblique relief orthogonal is 90 degrees
*/
public int planObliqueAngle = 90;
/**
* localGridModel encapsulates the settings and cashed intermediate results
* for computing a locally filtered grid for local hypsometric tinting.
*/
private final LocalGridModel localGridModel = new LocalGridModel();
/**
* Contains references to 2 grids for creating a bivariate color scheme.
*/
protected final BivariateColorRenderer bivariateColorRender = new BivariateColorRenderer();
/**
* Renderer for 2D LUT
*/
private final ColorLUT colorLUT = new ColorLUT();
public Model() {
predefinedColorRamps = new ArrayList<>();
float[] pos = new float[]{0.0F, 1.0F};
Color[] col = new Color[]{
Color.BLACK,
Color.WHITE};
predefinedColorRamps.add(new ColorRamp("Black-White", col, pos));
col = new Color[]{
Color.GRAY,
Color.WHITE};
predefinedColorRamps.add(new ColorRamp("Soft Gray", col, pos));
pos = new float[]{0.5F, 1.0F};
col = new Color[]{
Color.BLACK,
Color.WHITE};
predefinedColorRamps.add(new ColorRamp("Hard Gray", col, pos));
pos = new float[]{0.0F, 0.56F, 0.81F, 0.93F, 1.0F};
col = new Color[]{
Color.decode("#6d7ea1"),
Color.decode("#97a3ba"),
Color.decode("#bcbcbc"),
Color.decode("#dedace"),
Color.decode("#e8e8e8")};
predefinedColorRamps.add(new ColorRamp("Natural Light (Exposition)", col, pos));
pos = new float[]{0.0F, 0.42F, 0.73F, 0.88F, 1.0F};
col = new Color[]{
Color.decode("#526b75"),
Color.decode("#6a8e82"),
Color.decode("#a6b4a9"),
Color.decode("#e2d4ac"),
Color.decode("#f7f3b1")};
predefinedColorRamps.add(new ColorRamp("Swiss Style (Exposition)", col, pos));
pos = new float[]{0, 0.08f, 0.24f, 0.43f, 0.69f, 0.89f};
col = new Color[]{
new Color(120, 181, 141),
new Color(124, 172, 104),
new Color(190, 194, 107),
new Color(212, 218, 170),
new Color(225, 246, 244),
new Color(255, 255, 255)
};
predefinedColorRamps.add(new ColorRamp("Hypsometric", col, pos));
colorRamp = new ColorRamp(predefinedColorRamps.get(0));
}
/**
* Use one of the named color ramps. If an invalid name is passed, the color
* ramp does not change.
*
* @param name The name of the ColorRamp to use.
*/
public void selectColorRamp(String name) {
for (ColorRamp cr : predefinedColorRamps) {
if (cr.name.equals(name)) {
colorRamp = new ColorRamp(cr);
break;
}
}
}
/**
* Computes the weight for one level of the Laplacian pyramid.
*
* @param pyramidLevel the pyramid level. The level with the highest
* frequencies has a value of 0.
* @return the weight for that pyramid level between 0 and 1
*/
private float getPyramidLevelWeight(int pyramidLevel) {
if (pyramidLevel >= generalizationMaxLevels || generalizationMaxLevels <= 0) {
return 1;
}
// return (float) (1 / Math.pow(base, maxLevels - pyramidLevel));
// simplified:
// return (float) (Math.pow(base, pyramidLevel - maxLevels));
if (generalizationDetails == 1d) {
return 0;
}
double m, c;
if (generalizationDetails > 0) {
// a line of the form y = mx + c
// the line is crossing the positive horizontal x axis
// at generalizationDetails * generalizationMaxLevels
m = 1 / (generalizationMaxLevels * (1 - generalizationDetails));
c = generalizationDetails / (generalizationDetails - 1);
} else {
// a line of the form y = mx + c
// the line is crossing the positive vertical y axis at c = -b
c = -generalizationDetails;
m = (1 + generalizationDetails) / generalizationMaxLevels;
}
double w = m * pyramidLevel + c;
// clamp weight to [0..1]
return (float) Math.min(Math.max(0d, w), 1d);
}
/**
* re-computes generalized grid. Call this method whenever the
* generalization parameters or the Laplacian pyramid have changed.
*/
public void updateGeneralizedGrid() {
if (laplacianPyramid == null) {
return;
}
//long start = System.nanoTime();
if (isGeneralizing()) {
// compute weights for summing levels in Laplacian pyramid
float[] w = new float[laplacianPyramid.getLevels().length];
for (int i = 0; i < w.length; i++) {
w[i] = getPyramidLevelWeight(i);
}
// sum the Laplacian pyramids
generalizedGrid = laplacianPyramid.sumLevels(w, true);
// copy NaN values from original grid
new GridMaskOperator().operate(grid, generalizedGrid);
// scale the minimum and maximum values of the output generalized grid to
// the same range as the input grid.
new GridScaleToRangeOperator(gridMinMax).operate(generalizedGrid, generalizedGrid);
} else {
generalizedGrid = new GridCopyOperator().operate(grid);
}
generalizedSlopeGrid = new GridSlopeOperator().operate(generalizedGrid);
//System.out.println((System.nanoTime() - start) / 1000 / 1000 + "ms");
}
/**
* Creates a new BufferedImage
*
* @param scale Scale factor by which the created image will be larger than
* the generalized grid.
* @return A new image or null
*/
public BufferedImage createDestinationImage(int scale) {
if (backgroundVisualization == ColorVisualization.BIVARIATE) {
Grid grid1 = bivariateColorRender.getAttribute1Grid();
if (grid1 != null) {
return new BufferedImage(grid1.getCols(), grid1.getRows(), BufferedImage.TYPE_INT_ARGB);
}
}
if (generalizedGrid == null) {
return null;
}
//Get the number of columns and rows in the DEM
int cols = generalizedGrid.getCols() * scale;
int rows = generalizedGrid.getRows() * scale;
return new BufferedImage(cols, rows, BufferedImage.TYPE_INT_ARGB);
}
/**
* Render background image, such as shading or hypsometric tinting.
*
* @param destinationImage The background image will be rendered to this
* image.
* @param progressIndicator
* @return
*/
public BufferedImage renderBackgroundImage(BufferedImage destinationImage,
ProgressIndicator progressIndicator) {
if (destinationImage == null) {
return null;
}
// background visualization
if (backgroundVisualization == ColorVisualization.CONTINUOUS) {
// fill image with single color
Graphics2D graphics = (Graphics2D) destinationImage.getGraphics();
graphics.setColor(solidColor);
graphics.fillRect(0, 0, destinationImage.getWidth(), destinationImage.getHeight());
graphics.dispose();
} else {
Grid planObliqueGeneralizedGrid = generalizedGrid;
if (generalizedGrid != null) {
PlanObliqueOperator planObliqueOp = new PlanObliqueOperator(planObliqueAngle, gridMinMax[0]);
if (planObliqueAngle != 90) {
planObliqueGeneralizedGrid = planObliqueOp.operate(generalizedGrid);
}
}
// coloring and shading
ColorizerOperator colorizer = new ColorizerOperator(backgroundVisualization,
bivariateColorRender, colorLUT, progressIndicator);
colorizer.setColors(colorRamp.colors, colorRamp.colorPositions);
final Grid g;
final float min;
final float max;
if (backgroundVisualization == ColorVisualization.BIVARIATE) {
g = getBivariateColorRenderer().getAttribute1Grid();
float[] minMax = getBivariateColorRenderer().getAttribute1MinMax();
if (minMax == null) {
return null;
}
min = minMax[0];
max = minMax[1];
} else {
min = gridMinMax[0];
max = gridMinMax[1];
if (backgroundVisualization.isLocal()) {
g = localGridModel.getFilteredGrid();
} else {
g = planObliqueGeneralizedGrid;
}
}
colorizer.operate(g, destinationImage,
min, max,
azimuth, zenith, ambientLight, shadingVerticalExaggeration);
}
return destinationImage;
}
/**
* Render foreground visualization: illuminated contours
*
* @param destinationImage The foreground image will be rendered to this
* image.
* @param progressIndicator
* @return
*/
public BufferedImage renderForegroundImage(BufferedImage destinationImage,
ProgressIndicator progressIndicator) {
if (isRenderingForeground()) {
boolean illuminated = (foregroundVisualization == ILLUMINATED_CONTOURS);
IlluminatedContoursOperator op = setupIlluminatedContoursOperator(illuminated);
op.renderToImage(destinationImage, generalizedGrid,
generalizedSlopeGrid, progressIndicator);
}
return destinationImage;
}
/**
* Set the elevation grid.
*
* @param grid The new grid.
*/
public void setGrid(Grid grid) {
this.grid = grid;
// find minimum and maximum values in grid
gridMinMax = grid.getMinMax();
// create a Gaussian pyramids
GaussianPyramid gaussianPyramid = new GaussianPyramid(grid);
// create the Laplacian pyramid
laplacianPyramid = new LaplacianPyramid();
laplacianPyramid.createPyramid(gaussianPyramid.getPyramid());
updateGeneralizedGrid();
localGridModel.setGrid(generalizedGrid, gridMinMax, laplacianPyramid);
}
/**
* Returns the original ungeneralized grid.
*
* @return The ungeneralized grid.
*/
public Grid getGrid() {
return grid;
}
/**
* Returns the generalized grid.
*
* @return the generalizedGrid
*/
public Grid getGeneralizedGrid() {
return generalizedGrid;
}
/**
* Returns the width and height of the un-scaled rendered image.
* @return
*/
public Dimension getGridDimensionForDisplay() {
final Grid g;
if (backgroundVisualization == ColorVisualization.BIVARIATE
&& bivariateColorRender.hasGrids()) {
g = bivariateColorRender.getAttribute1Grid();
} else {
g = grid;
}
return g == null ? null : new Dimension(g.getCols(), g.getRows());
}
/**
* Returns the locally filtered grid.
*
* @return the locally filtered grid.
*/
public Grid getLocalGrid() {
return localGridModel.getFilteredGrid();
}
/**
* Returns a grid with slope values for the generalized grid.
*
* @return The generalized grid.
*/
public Grid getGeneralizedSlopeGrid() {
return generalizedSlopeGrid;
}
/**
* Initializes an illuminatedIlluminatedContoursOperator with the current
* model settings.
*
* @param illuminated If true illuminated contours are created, otherwise
* shaded contours are created.
* @return
*/
public IlluminatedContoursOperator setupIlluminatedContoursOperator(
boolean illuminated) {
return new IlluminatedContoursOperator(
illuminated,
contoursIlluminatedColor,
contoursShadowedColor,
contoursShadowWidthLow,
contoursShadowWidthHigh,
contoursIlluminatedWidthLow,
contoursIlluminatedWidthHigh,
contoursMinWidth,
contoursMinDist,
azimuth,
contoursInterval,
contoursGradientAngle,
contoursAspectGaussBlur,
contoursTransitionAngle,
gridMinMax);
}
/**
* @return the generalizationDetails
*/
public double getGeneralizationDetails() {
return generalizationDetails;
}
/**
* Returns true if the grid is being generalized.
*
* @return
*/
public boolean isGeneralizing() {
return generalizationDetails > -1d;
}
/**
* Returns true when contour lines need to be rendered in the foreground
*
* @return
*/
public boolean isRenderingForeground() {
return foregroundVisualization != ForegroundVisualization.NONE;
}
/**
* @param generalizationDetails the generalizationDetails to set
*/
public void setGeneralizationDetails(double generalizationDetails) {
if (generalizationDetails < -1 || generalizationDetails > 1) {
throw new IllegalArgumentException();
}
this.generalizationDetails = generalizationDetails;
}
public double getLocalGridHighPassWeight() {
return localGridModel.getHighPassWeight();
}
public int getLocalGridStandardDeviationLevels() {
return localGridModel.getLocalGridStandardDeviationLevels();
}
public void setLocalGridHighPassWeight(double highPassWeight) {
localGridModel.setHighPassWeight(highPassWeight);
}
public void setLocalGridStandardDeviationLevels(int levels) {
localGridModel.setLocalGridStandardDeviationLevels(levels);
}
public void scaleGrid(float scale) {
GridScaleOperator op = new GridScaleOperator(scale);
op.operate(grid, grid);
setGrid(grid);
}
public void verticallyOffsetGrid(float offset) {
GridAddOperator op = new GridAddOperator(offset);
op.operate(grid, grid);
setGrid(grid);
}
public void voidGridValue(float v) {
GridVoidOperator op = new GridVoidOperator(v);
op.operate(grid, grid);
setGrid(grid);
}
/**
* @return the bivariateColorRender
*/
public BivariateColorRenderer getBivariateColorRenderer() {
return bivariateColorRender;
}
/**
* @return the colorLUT
*/
public ColorLUT getColorLUT() {
return colorLUT;
}
public ColorLUTInterface getColorLUTRenderer(){
if (backgroundVisualization == ColorVisualization.EXPOSITION_ELEVATION) {
return colorLUT;
} else {
return bivariateColorRender;
}
}
}