package org.vfny.geoserver.wms.responses.legend.raster;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.geotools.styling.ColorMapEntry;
import org.opengis.style.ColorMap;
import org.vfny.geoserver.wms.responses.LegendUtils;
import org.vfny.geoserver.wms.responses.LegendUtils.HAlign;
import org.vfny.geoserver.wms.responses.LegendUtils.VAlign;
/**
* This class mimics a simple cell for the final {@link ColorMap} legend reprensentation.
*
* @author Simone Giannecchini, GeoSolutions SAS
*
*/
@SuppressWarnings("deprecation")
abstract class Cell{
protected final Color bkgColor;
protected final double bkgOpacity;
protected final String text;
protected final HAlign hAlign;
protected final VAlign vAlign;
protected final Dimension requestedDimension;
protected final Font labelFont;
protected final Color labelFontColor;
protected final boolean fontAntiAliasing;
protected final Color borderColor;
protected Cell(
final Color bkgColor,
final double bkgOpacity,
final String text,
final HAlign hAlign,
final VAlign vAlign,
final Dimension requestedDimension,
final Font labelFont,
final Color labelFontColor,
final boolean fontAntiAliasing,
final Color borderColor) {
this.bkgColor = bkgColor;
this.bkgOpacity = bkgOpacity;
this.text = text;
this.hAlign = hAlign;
this.vAlign = vAlign;
this.requestedDimension = requestedDimension;
this.labelFont = labelFont;
this.labelFontColor = labelFontColor;
this.fontAntiAliasing=fontAntiAliasing;
this.borderColor=borderColor;
}
public abstract void draw(final Graphics2D graphics,final Rectangle2D clipBox, final boolean completeBorder);
/**
* Retrieves the preferred dimension for this {@link Cell} element within the provided graphics element.
*
* @param graphics {@link Graphics2D} object to use for computing the preferred dimension
* @return the preferred dimension for this {@link Cell} element within the provided graphics element.
*/
public abstract Dimension getPreferredDimension(final Graphics2D graphics);
}
/**
* This class mimics a simple row for the final {@link ColorMap} legend representation.
*
* @author Simone Giannecchini, GeoSolutions SAS
*
*/
abstract class Row{
private final List<Cell> cells= new ArrayList<Cell>();
Row(){
}
Row(final List<Cell> columns){
columns.addAll(columns);
}
protected Cell get(final int index){
return cells.get(index);
}
protected void add(final Cell cell){
cells.add(cell);
}
}
abstract class ColorMapEntryLegendBuilder extends Row{
protected ColorMapEntryLegendBuilder() {
super();
}
protected ColorMapEntryLegendBuilder(List<Cell> columns) {
super(columns);
}
protected ColorMapEntryLegendBuilder(
final ColorManager colorManager,
final TextManager labelManager,
final TextManager ruleManager) {
super(Arrays.asList(colorManager,ruleManager,labelManager));
}
public boolean hasLabel() {
return hasLabel;
}
protected boolean hasLabel;
public Cell getRuleManager() {
return get(1);
}
public Cell getLabelManager() {
return get(2);
}
public Cell getColorManager() {
return get(0);
}
}
class SingleColorMapEntryLegendBuilder extends ColorMapEntryLegendBuilder{
@SuppressWarnings("deprecation")
public SingleColorMapEntryLegendBuilder(
final List<ColorMapEntry> cMapEntries,
final HAlign hAlign,
final VAlign vAling,
final Color bkgColor,
final double bkgOpacity,
final String text,
final Dimension requestedDimension,
final Font labelFont,
final Color labelFontColor,
final boolean fontAntiAliasing,
final Color borderColor) {
final ColorMapEntry currentCME = cMapEntries.get(0);
Color color = LegendUtils.color(currentCME);
final double opacity = LegendUtils.getOpacity(currentCME);
color=new Color(color.getRed(),color.getGreen(),color.getBlue(),(int) (255*opacity));
super.add( new SimpleColorManager(color,opacity,requestedDimension, borderColor));
final String label = currentCME.getLabel();
final double quantity = LegendUtils.getQuantity(currentCME);
final String symbol = " = ";
String rule = Double.toString(quantity)+" "+symbol+" x";
super.add( new TextManager(rule, vAling, hAlign, bkgColor, requestedDimension, labelFont, labelFontColor, fontAntiAliasing, borderColor));
// add the label the label to the rule so that we draw all text just once
if(label!=null)
{
hasLabel=true;
super.add( new TextManager(label, vAling, hAlign, bkgColor, requestedDimension, labelFont, labelFontColor, fontAntiAliasing, borderColor));
}
else
super.add( null);
}
}
class RampColorMapEntryLegendBuilder extends ColorMapEntryLegendBuilder{
@SuppressWarnings("deprecation")
public RampColorMapEntryLegendBuilder(
final List<ColorMapEntry> mapEntries,
final HAlign hAlign,
final VAlign vAling,
final Color bkgColor,
final double bkgOpacity,
final String text,
final Dimension requestedDimension,
final Font labelFont,
final Color labelFontColor,
final boolean fontAntiAliasing,
final Color borderColor) {
final ColorMapEntry previousCME = mapEntries.get(0);
final ColorMapEntry currentCME = mapEntries.get(1);
boolean leftEdge;
if(previousCME==null)
leftEdge=true;
else
leftEdge=false;
Color previousColor;
if(!leftEdge){
previousColor=LegendUtils.color(previousCME);
final double opacity=LegendUtils.getOpacity(previousCME);
previousColor=new Color(previousColor.getRed(),previousColor.getGreen(),previousColor.getBlue(),(int) (255*opacity+0.5));
}
else
previousColor=null;
Color color = LegendUtils.color(currentCME);
double opacity = LegendUtils.getOpacity(currentCME);
color=new Color(color.getRed(),color.getGreen(),color.getBlue(),(int) (255*opacity));
super.add( new GradientColorManager(color,opacity,previousColor, requestedDimension, borderColor));
String label = currentCME.getLabel();
double quantity = LegendUtils.getQuantity(currentCME);
String symbol = leftEdge?" > ":" = ";
String rule = leftEdge?
Double.toString(quantity)+" "+symbol+" x":
Double.toString(quantity)+" "+symbol+" x";
super.add( new TextManager(rule, vAling, hAlign, bkgColor, requestedDimension, labelFont, labelFontColor, leftEdge, borderColor));
// add the label the label to the rule so that we draw all text just once
if(label!=null)
{
hasLabel=true;
super.add( new TextManager(label, vAling, hAlign, bkgColor, requestedDimension, labelFont, labelFontColor, leftEdge, borderColor));
}
else
super.add( null);
}
}
class ClassesEntryLegendBuilder extends ColorMapEntryLegendBuilder{
@SuppressWarnings("deprecation")
public ClassesEntryLegendBuilder(
final List<ColorMapEntry> mapEntries,
final HAlign hAlign,
final VAlign vAling,
final Color bkgColor,
final double bkgOpacity,
final String text,
final Dimension requestedDimension,
final Font labelFont,
final Color labelFontColor,
final boolean fontAntiAliasing,
final Color borderColor) {
final ColorMapEntry previousCME = mapEntries.get(0);
final ColorMapEntry currentCME = mapEntries.get(1);
boolean leftEdge;
if(previousCME==null)
leftEdge=true;
else
leftEdge=false;
Color color = LegendUtils.color(currentCME);
final double opacity = LegendUtils.getOpacity(currentCME);
color=new Color(color.getRed(),color.getGreen(),color.getBlue(),(int) (255*opacity));
super.add( new SimpleColorManager(color,opacity, requestedDimension, borderColor));
String label =currentCME.getLabel();
double quantity1 = leftEdge?LegendUtils.getQuantity(currentCME):LegendUtils.getQuantity(previousCME);
double quantity2 = LegendUtils.getQuantity(currentCME);
String symbol1=null,symbol2=null;
if(leftEdge)
symbol1=" < ";
else
{
symbol1=" <= ";
symbol2=" < ";
}
String rule=leftEdge?
Double.toString(quantity1)+" "+symbol1+" x":
Double.toString(quantity1)+" "+symbol1+" x "+symbol2+" "+ Double.toString(quantity2);
super.add( new TextManager(rule, vAling, hAlign, bkgColor, requestedDimension, labelFont, labelFontColor, leftEdge, borderColor));
// add the label the label to the rule so that we draw all text just once
if(label!=null)
{
hasLabel=true;
super.add( new TextManager(label, vAling, hAlign, bkgColor, requestedDimension, labelFont, labelFontColor, leftEdge, borderColor));
}
else
super.add( null);
}
}
/**
* This class mimics a simple color cell for the final {@link ColorMap} legend representation. It is responsible for
* for drawing colors for a {@link ColorMapEntry}.
*
* @author Simone Giannecchini, GeoSolutions SAS
*
*/
abstract class ColorManager extends Cell{
public ColorManager(
final Color color,
final double opacity,
final Dimension requestedDimension,
final Color borderColor) {
super(color, opacity, null,null,null, requestedDimension, null, null, false, borderColor);
}
public abstract void draw(final Graphics2D graphics,final Rectangle2D clipBox, final boolean completeBorder);
@Override
public Dimension getPreferredDimension(final Graphics2D graphics){
return new Dimension(requestedDimension);
}
}
class SimpleColorManager extends ColorManager{
public SimpleColorManager(
final Color color,
final double opacity,
final Dimension requestedDimension,
final Color borderColor) {
super(color, opacity,requestedDimension, borderColor);
}
@Override
public void draw(final Graphics2D graphics, final Rectangle2D clipBox, final boolean completeBorder) {
//bkgColor fill
if(bkgOpacity>0){
// OPAQUE
final Color oldColor=graphics.getColor();
final Color newColor= new Color(bkgColor.getRed(),bkgColor.getGreen(),bkgColor.getBlue(),(int) (255*bkgOpacity+0.5));
graphics.setColor(newColor);
graphics.fill(clipBox);
//make bkgColor customizable
graphics.setColor(borderColor);
if(completeBorder)
{
final int minx=(int) (clipBox.getMinX()+0.5);
final int miny=(int) (clipBox.getMinY()+0.5);
final int w=(int) (clipBox.getWidth()+0.5)-1;
final int h=(int)(clipBox.getHeight()+0.5)-1;
graphics.draw(new Rectangle2D.Double(minx,miny,w,h));
}
//restore bkgColor
graphics.setColor(oldColor);
}
else
{
// TRANSPARENT
final Color oldColor=graphics.getColor();
//white background
graphics.setColor(Color.white);
graphics.fill(clipBox);
//now the red cross
graphics.setColor(Color.RED);
final int minx=(int) (clipBox.getMinX()+0.5);
final int miny=(int) (clipBox.getMinY()+0.5);
final int maxx=(int) (minx+clipBox.getWidth()-1+0.5);
final int maxy=(int)(miny+clipBox.getHeight()-1+0.5);
graphics.drawLine(minx,miny,maxx,maxy);
graphics.drawLine(minx,maxy,maxx,miny);
graphics.setColor(borderColor);
if(completeBorder)
{
final int w=(int) (clipBox.getWidth()+0.5)-1;
final int h=(int)(clipBox.getHeight()+0.5)-1;
graphics.draw(new Rectangle2D.Double(minx,miny,w,h));
}
//restore bkgColor
graphics.setColor(oldColor);
}
}
}
class GradientColorManager extends SimpleColorManager{
@Override
public Dimension getPreferredDimension(Graphics2D graphics) {
//twice as much space for the Height to account for the gradient
return new Dimension(requestedDimension.width,(int) (1.5*requestedDimension.height+0.5));
}
private Color previousColor=null;
private boolean leftEdge;
public GradientColorManager(
final Color color,
final double opacity,
final Color previousColor,
final Dimension requestedDimension,
final Color borderColor) {
super(color, opacity,requestedDimension, borderColor);
this.previousColor=previousColor;
if(previousColor==null)
leftEdge=true;
}
@Override
public void draw(final Graphics2D graphics, final Rectangle2D clipBox, final boolean completeBorder) {
// getting clipbox dimensions
final double minx=clipBox.getMinX();
final double miny=clipBox.getMinY();
final double w=clipBox.getWidth();
final double h=clipBox.getHeight();
// GRADIENT
if(!leftEdge){
//rectangle for the gradient
final Rectangle2D.Double rectLegend= new Rectangle2D.Double(
minx,
miny,
w,
h/2);
//gradient paint
final Paint oldPaint=graphics.getPaint();
final GradientPaint paint=new GradientPaint(
(float)minx,(float)miny,previousColor,
(float)minx,(float)(miny+h/2),bkgColor);
// do the magic
graphics.setPaint(paint);
graphics.fill(rectLegend);
//restore paint
graphics.setPaint(oldPaint);
}
//COLOR BOX
//careful with handling the leftEdge case
final Rectangle2D rectLegend = new Rectangle2D.Double(
minx,
miny+(leftEdge?0:h/2),
w,
!leftEdge?h/2:h);
super.draw(graphics, rectLegend, completeBorder);
if(!completeBorder)
{
final Color oldColor=graphics.getColor();
//make bkgColor customizable
graphics.setColor(borderColor);
final int minx_=(int) (clipBox.getMinX()+0.5);
final int maxx=(int) (minx+clipBox.getWidth()+0.5)-1;
final int maxy=(int)(miny+clipBox.getHeight()+0.5)-1;
graphics.drawLine(minx_,maxy,maxx,maxy);
//restore bkgColor
graphics.setColor(oldColor);
}
}
}
/**
* This class mimics a simple text cell for the final {@link ColorMap} legend representation.
*
* @author Simone Giannecchini, GeoSolutions SAS
*
*/
class TextManager extends Cell{
public TextManager(
final String text,
final VAlign vAlign,
final HAlign hAlign,
final Color bkgColor,
final Dimension requestedDimension,
final Font labelFont,
final Color labelFontColor,
final boolean fontAntiAliasing,
final Color borderColor) {
super(bkgColor, 1.0, text,hAlign,vAlign,requestedDimension,labelFont,labelFontColor,fontAntiAliasing, borderColor);
}
@Override
public Dimension getPreferredDimension(final Graphics2D graphics){
//get old font
final Font oldFont=graphics.getFont();
//set new font
graphics.setFont(labelFont);
//computing label dimension and creating buffered image on which we can draw the label on it
final int labelHeight = (int) Math.ceil(graphics.getFontMetrics().getStringBounds(text, graphics).getHeight());
final int labelWidth = (int) Math.ceil(graphics.getFontMetrics().getStringBounds(text, graphics).getWidth());
//restore the old font
graphics.setFont(oldFont);
return new Dimension(labelWidth,labelHeight);
}
public void draw(final Graphics2D graphics,final Rectangle2D clipBox ,final boolean completeBorder){
//save old font
final Font oldFont=graphics.getFont();
//set font and font color and the antialising
graphics.setColor(labelFontColor);
graphics.setFont(labelFont);
if(fontAntiAliasing)
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
//Halign==center vAlign==bottom
final double minx=clipBox.getMinX();
final double miny=clipBox.getMinY();
final double w=clipBox.getWidth();
final double h=clipBox.getHeight();
final Dimension dimension=getPreferredDimension(graphics);
//where do we draw?
final int xText;
switch (hAlign) {
case CENTERED:
xText=(int)(minx+(w-dimension.getWidth())/2.0+0.5);
break;
case LEFT:
xText=(int)(minx+0.5);
break;
case RIGHT:
xText=(int)(minx+(w-dimension.getWidth())+0.5);
break;
case JUSTIFIED:
throw new UnsupportedOperationException("Unsupported");
default:
throw new IllegalStateException("Unsupported horizontal alignment " +hAlign);
}
final int yText ;
switch (vAlign) {
case BOTTOM:
yText=(int)(miny+h-graphics.getFontMetrics().getDescent()+0.5) ;
break;
case TOP:
yText=(int)(miny+graphics.getFontMetrics().getHeight()+0.5) ;
break;
case MIDDLE:
yText=(int)(miny+(h+graphics.getFontMetrics().getHeight())/2+0.5) ;
break;
default:
throw new IllegalStateException("Unsupported vertical alignment " +vAlign);
}
//draw
graphics.drawString(text, xText,yText);
//restore the old font
graphics.setFont(oldFont);
}
}