/*
* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2011, Refractions Research Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
* License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
*/
package org.locationtech.udig.style.sld.editor.raster;
import java.io.File;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import org.locationtech.udig.catalog.ID;
import org.locationtech.udig.catalog.IGeoResource;
import org.locationtech.udig.project.internal.Layer;
import org.locationtech.udig.project.internal.StyleBlackboard;
import org.locationtech.udig.style.sld.SLDContent;
import org.locationtech.udig.style.sld.SLDPlugin;
import org.locationtech.udig.style.sld.editor.CustomDynamicPalette;
import org.locationtech.udig.style.sld.editor.CustomPalettesLoader;
import org.locationtech.udig.style.sld.editor.StyleEditor;
import org.locationtech.udig.style.sld.editor.StyleEditorPage;
import org.locationtech.udig.style.sld.editor.internal.BrewerPaletteLabelProvider;
import org.locationtech.udig.style.sld.editor.raster.PredefinedColorRules.PredefinedColorRule;
import org.locationtech.udig.style.sld.editor.raster.ValueFormatter.DataType;
import org.locationtech.udig.style.sld.internal.Messages;
import org.locationtech.udig.ui.PlatformGIS;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.geotools.brewer.color.BrewerPalette;
import org.geotools.brewer.color.ColorBrewer;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.styling.ColorMap;
import org.geotools.styling.ColorMapEntry;
import org.geotools.styling.ColorMapEntryImpl;
import org.geotools.styling.ColorMapImpl;
import org.geotools.styling.RasterSymbolizer;
import org.geotools.styling.SLD;
import org.geotools.styling.SLDTransformer;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridCoverageReader;
/**
* Style editor page for single banded rasters
* that allow users to create styles for
* each ColorMap type -
* ColorMap.TYPE_RAMP,
* ColorMap.TYPE_INTERVALS,
* ColorMap.TYPE_VALUES
*
* @author Emily Gouge (Refractions Research Inc.)
*
*/
public class SingleBandEditorPage extends StyleEditorPage {
/**
* Maximum number of unique values supported by
* the geotools styling.
*/
public static final int MAX_ENTRIES = 256;
private static final String SLD_EXTENSION = ".sld"; //$NON-NLS-1$
private double[] noDataValues = null;
private StyleFactory sf = CommonFactoryFinder.getStyleFactory(null);
private LabelProvider brewerLblProvider = new BrewerPaletteLabelProvider();
final private IColorMapTypePanel[] stylePanels = new IColorMapTypePanel[]{
new UniqueValuesPanel(this),
new RampValuesPanel(this),
new IntervalValuesPanel(this)
};
private String errorMessage = null;
private ComboViewer cmbThemingStyle;
private ComboViewer cmbPalette;
private Composite tableComp;
private ColorBrewer brewer = null;
private boolean reverseColors = false;
private ValueFormatter formatter;
private PredefinedColorRules colorRules;
/**
* Creates a new style editor page
*/
public SingleBandEditorPage() {
super();
formatter = new ValueFormatter();
}
/**
* @see org.locationtech.udig.style.sld.IEditorPage#okToLeave()
*/
@Override
public boolean okToLeave() {
return errorMessage == null;
}
@Override
public boolean performOk() {
return performApply();
}
@Override
public boolean performApply() {
try{
updateStyle();
}catch (Exception ex){
SLDPlugin.log("Error applying style.", ex); //$NON-NLS-1$
MessageDialog.openError(getShell(), Messages.SingleBandEditorPage_Error, Messages.SingleBandEditorPage_ApplyError + ex.getLocalizedMessage());
return false;
}
return true;
}
@Override
public void refresh() {
init();
}
@Override
public void createPageContent(Composite parent) {
Composite main = new Composite(parent, SWT.NONE);
main.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
main.setLayout(new GridLayout(3, false));
Label lbl = new Label(main, SWT.NONE);
lbl.setText(Messages.SingleBandEditorPage_ThemingStyleLabel);
cmbThemingStyle = new ComboViewer(main, SWT.DROP_DOWN | SWT.READ_ONLY);
cmbThemingStyle.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, false,
false, 2, 1));
((GridData)cmbThemingStyle.getControl().getLayoutData()).widthHint = 150;
cmbThemingStyle.setContentProvider(ArrayContentProvider.getInstance());
cmbThemingStyle.setInput(stylePanels);
cmbThemingStyle.setLabelProvider(new LabelProvider(){
@Override
public String getText(Object element){
if (element instanceof IColorMapTypePanel){
return ((IColorMapTypePanel) element).getName();
}
return super.getText(element);
}
});
lbl = new Label(main, SWT.NONE);
lbl.setText(Messages.SingleBandEditorPage_ColorPaletteLabel);
cmbPalette = createPaletteViewer(main);
final Button btnTest = new Button(main, SWT.PUSH);
btnTest.setText(Messages.SingleBandEditorPage_ComputeBreaksButtonText);
btnTest.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
btnTest.addSelectionListener(new SelectionAdapter(){
@Override
public void widgetSelected(SelectionEvent e){
getCurrentSelection().computeValues();
}
});
tableComp = new Composite(main, SWT.NONE);
tableComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
tableComp.setLayout(new StackLayout());
final HashMap<IColorMapTypePanel, Composite> stacks = new HashMap<IColorMapTypePanel, Composite>();
for (IColorMapTypePanel pnl : stylePanels){
Composite stack = pnl.createControl(tableComp);
stacks.put(pnl, stack);
}
Composite linkPnl = new Composite(main, SWT.NONE);
linkPnl.setLayout(new GridLayout(4, false));
linkPnl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));
Link lnk = new Link(linkPnl, SWT.NONE);
lnk.setText("<a>" + Messages.SingleBandEditorPage_ReverseColorLabel + "</a>"); //$NON-NLS-1$ //$NON-NLS-2$
lnk.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
reverseColors = !reverseColors;
if (getCurrentSelection() != null){
Object x = ((IStructuredSelection)cmbPalette.getSelection()).getFirstElement();
if (x instanceof BrewerPalette){
BrewerPalette palette = (BrewerPalette) ((IStructuredSelection)cmbPalette.getSelection()).getFirstElement();
getCurrentSelection().setColorPalette(palette, reverseColors);
}else if (x instanceof PredefinedColorRule){
ColorMap cm = ((PredefinedColorRule)x).getColorMap();
if (cm != null && getCurrentSelection() != null){
//flip colors
if (reverseColors){
ColorMap reverse = new ColorMapImpl();
for (int i = 0; i < cm.getColorMapEntries().length; i ++){
ColorMapEntry entry = cm.getColorMapEntries()[i];
ColorMapEntry clone = new ColorMapEntryImpl();
clone.setColor(cm.getColorMapEntry(cm.getColorMapEntries().length - 1 - i).getColor());
clone.setLabel(entry.getLabel());
clone.setQuantity(entry.getQuantity());
clone.setOpacity(entry.getOpacity());
reverse.addColorMapEntry(clone);
}
cm = reverse;
}
getCurrentSelection().init(cm);
}
}
}
}
});
Label lblSep = new Label(linkPnl, SWT.SEPARATOR | SWT.VERTICAL);
GridData gd = new GridData(SWT.FILL, SWT.FILL, false, false);
gd.heightHint = 10;
lblSep.setLayoutData(gd);
lnk = new Link(linkPnl, SWT.NONE);
lnk.setText("<a>" + Messages.SingleBandEditorPage_FormatExportLink + "</a>"); //$NON-NLS-1$ //$NON-NLS-2$
lnk.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
getFormat();
}
});
Link lnk2 = new Link(linkPnl, SWT.NONE);
lnk2.setText("<a>" + Messages.SingleBandEditorPage_OneClickExportLink + "</a>"); //$NON-NLS-1$ //$NON-NLS-2$
lnk2.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false));
lnk2.setToolTipText(Messages.SingleBandEditorPage_OneClickTooltip);
lnk2.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
oneClickExport();
}
});
cmbThemingStyle.getCombo().addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
IColorMapTypePanel panel = getCurrentSelection();
if (panel != null){
((StackLayout)tableComp.getLayout()).topControl = stacks.get(panel);
tableComp.layout();
btnTest.setText(panel.getComputeValuesLabel());
btnTest.getParent().layout();
Object x = ((IStructuredSelection)cmbPalette.getSelection()).getFirstElement();;
if (x instanceof BrewerPalette){
BrewerPalette palette = (BrewerPalette) x;
getCurrentSelection().setInitialColorPalette(palette);
}else if (x instanceof PredefinedColorRule){
ColorMap cm = ((PredefinedColorRule)x).getColorMap();
if (cm != null && getCurrentSelection() != null){
getCurrentSelection().init(cm);
}
}
}
}
});
cmbPalette.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
Object x = ((IStructuredSelection)cmbPalette.getSelection()).getFirstElement();;
if (x instanceof BrewerPalette){
BrewerPalette palette = (BrewerPalette) x;
if (getCurrentSelection() != null){
getCurrentSelection().setColorPalette(palette, reverseColors);
}
}else if (x instanceof PredefinedColorRule){
ColorMap cm = ((PredefinedColorRule)x).getColorMap();
if (cm != null && getCurrentSelection() != null){
getCurrentSelection().init(cm);
}
}
}
});
cmbThemingStyle.getControl().addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
if (colorRules != null){
colorRules.dispose();
}
}
});
init();
}
private void getFormat(){
FormatDialog fd = new FormatDialog(getShell(), this.formatter);
if (fd.open() == FormatDialog.OK){
if (fd.getSelectedDataType() != null){
formatter.setDataType(fd.getSelectedDataType());
formatter.setNumberFormatter(null);
}else if (fd.getSelectedDataType() == null && fd.getCustom() == null){
formatter.setDataType(null);
formatter.setNumberFormatter(null);
}else{
formatter.setNumberFormatter(fd.getCustom());
}
for (IColorMapTypePanel pnl : stylePanels){
pnl.refresh();
}
}
}
/*
* Performs a single click export which exports
* the style to a .sld file beside the associated layer file.
* Does nothing for non-file based layers.
*/
private void oneClickExport(){
IGeoResource geoResource = getSelectedLayer().getGeoResource();
ID id = geoResource.getID();
if (id.isFile()) {
try {
File file = id.toFile();
SLDTransformer aTransformer = new SLDTransformer();
aTransformer.setIndentation(StyleEditor.INDENT);
String xml = aTransformer.transform(getSLD());
File newFile = new File(file.getParent(), FilenameUtils
.getBaseName(file.getAbsolutePath())
+ SLD_EXTENSION);
FileUtils.writeStringToFile(newFile, xml);
MessageDialog.openInformation(getShell(), Messages.SingleBandEditorPage_ExportOkDialogTitle, Messages.SingleBandEditorPage_ExportOkDialogMessage);
} catch (Exception e1) {
MessageDialog.openError(getShell(), Messages.SingleBandEditorPage_ErrorDialogTitle,
Messages.SingleBandEditorPage_ErrorMessage);
e1.printStackTrace();
}
} else {
MessageDialog.openWarning(getShell(), Messages.SingleBandEditorPage_WarningDialogTitle,
Messages.SingleBandEditorPage_WarningMessage);
}
}
/**
*
* @return the current selected color map panel
*/
private IColorMapTypePanel getCurrentSelection(){
return (IColorMapTypePanel) ((IStructuredSelection)cmbThemingStyle.getSelection()).getFirstElement();
}
/**
* If you call then function you MUST dispose of the reader
* when you are finished with it!
*
* @return the grid coverage associated with the current
* layer being styled
*/
public GridCoverageReader getGridCoverageReader(){
try {
GridCoverageReader reader = getSelectedLayer().getGeoResource().resolve(GridCoverageReader.class, null);
return reader;
}catch (Exception ex){
SLDPlugin.log("Error getting grid coverage.", ex); //$NON-NLS-1$
}
return null;
}
public static final void sortEntries(ColorMapEntry[] entries){
Arrays.sort(entries, new Comparator<ColorMapEntry>(){
@Override
public int compare(ColorMapEntry c0, ColorMapEntry c1) {
Number v1 = (Number) c0.getQuantity().evaluate(null, Double.class);
Number v2 = (Number) c1.getQuantity().evaluate(null, Double.class);
return ((Double)v1.doubleValue()).compareTo(v2.doubleValue());
}});
}
/**
* Updates the style of the current layer
* with the style specified the current style
* panel.
*
* @throws Exception
*/
private void updateStyle() throws Exception{
RasterSymbolizer rasterSym = sf.createRasterSymbolizer();
ColorMap colorMap = getCurrentSelection().getColorMap();
rasterSym.setColorMap(colorMap);
Style newStyle = SLD.wrapSymbolizers(rasterSym);
newStyle.setName(((Layer)getSelectedLayer()).getName() );
StyleBlackboard styleBlackboard = getSelectedLayer().getStyleBlackboard();
// put style back on blackboard
styleBlackboard.put(SLDContent.ID, newStyle);
styleBlackboard.setSelected(new String[]{SLDContent.ID});
Object x = ((IStructuredSelection)cmbPalette.getSelection()).getFirstElement();
if (x instanceof BrewerPalette){
BrewerPalette palette = (BrewerPalette) x;
if (palette != null){
styleBlackboard.put("org.locationtech.udig.style.raster.palette", palette.getName()); //$NON-NLS-1$
}
}else if (x instanceof PredefinedColorRule){
styleBlackboard.put("org.locationtech.udig.style.raster.palette", ((PredefinedColorRule) x).getName()); //$NON-NLS-1$
}
}
private ComboViewer createPaletteViewer(Composite parent) {
ComboViewer thiscv = new ComboViewer(parent, SWT.DROP_DOWN
| SWT.READ_ONLY);
thiscv.getControl().setLayoutData(
new GridData(SWT.FILL, SWT.FILL, false, false, 2, 1));
((GridData)thiscv.getControl().getLayoutData()).widthHint = 150;
// list of matching palettes
thiscv.setLabelProvider(new LabelProvider(){
public String getText(Object element){
if (element instanceof BrewerPalette){
return brewerLblProvider.getText(element);
}else if (element instanceof String){
return (String)element;
}else if (element instanceof PredefinedColorRule){
return ((PredefinedColorRule) element).getName();
}
return super.getText(element);
}
});
thiscv.setContentProvider(ArrayContentProvider.getInstance());
return thiscv;
}
private ColorBrewer getBrewer() {
if (brewer == null) {
brewer = PlatformGIS.getColorBrewer();
// add custom palettes
List<BrewerPalette> palettesList = CustomPalettesLoader.PALETTESLIST;
for (BrewerPalette brewerPalette : palettesList) {
brewer.registerPalette(brewerPalette);
}
// add a dynamic one that support everything
CustomDynamicPalette customDynamicPalette = new CustomDynamicPalette(
CustomDynamicPalette.TABLE.RAINBOW);
brewer.registerPalette(customDynamicPalette);
customDynamicPalette = new CustomDynamicPalette(
CustomDynamicPalette.TABLE.GREY);
brewer.registerPalette(customDynamicPalette);
}
return brewer;
}
@Override
public String getLabel() {
return null;
}
@Override
public boolean performCancel() {
return false;
}
@Override
public void gotFocus() {
cmbPalette.getControl().setFocus();
}
@Override
public void styleChanged(Object source) {
}
/**
*
* @return the nodata values associated with the grid coverate
* <code>null</code> if no, no data values found
*/
public double[] getNoDataValues(){
return this.noDataValues;
}
private void init(){
Layer l = getSelectedLayer();
for (IColorMapTypePanel pnl : stylePanels){
pnl.setFormatter(this.formatter);
}
try{
GridCoverage coverage = l.getGeoResource().resolve(GridCoverage.class, null);
if (coverage.getNumSampleDimensions() > 0){
formatter.setRawDataType(coverage.getSampleDimension(0).getSampleDimensionType());
this.noDataValues = coverage.getSampleDimension(0).getNoDataValues();
}
}catch (Exception ex){
//eat me
}
if (colorRules != null){
colorRules.dispose();
colorRules = null;
}
colorRules = new PredefinedColorRules(getGridCoverageReader(), this.noDataValues);
Style style = (Style) l.getStyleBlackboard().get(SLDContent.ID);
RasterSymbolizer symbolizer = SLD.rasterSymbolizer(style);
if (symbolizer == null || symbolizer.getColorMap() == null){
//no symbolizer
}else{
ColorMap cm = symbolizer.getColorMap();
for (IColorMapTypePanel pnl : stylePanels){
if (pnl.canSupport(cm.getType())){
pnl.init(cm);
cmbThemingStyle.setSelection(new StructuredSelection(pnl));
cmbThemingStyle.getControl().notifyListeners(SWT.Selection, new Event());
}
}
if (formatter.getRawDataType() == DataType.INTEGER){
//if some of the entries are doubles we want to format in double
//regardless of the fact that the raster is an integer raster.
try{
for (ColorMapEntry e : cm.getColorMapEntries()){
Double dvalue = (Double) e.getQuantity().evaluate(null, Double.class);
Integer ivalue = (Integer) e.getQuantity().evaluate(null, Integer.class);
if (ivalue.doubleValue() != dvalue){
formatter.setDataType(DataType.DOUBLE);
}
}
}catch (Exception ex){
//eatme
}
}
}
//set inputs based on brewer and colorRules
List<Object> inputs = new ArrayList<Object>();
BrewerPalette[] palettes = getBrewer().getPalettes(ColorBrewer.ALL);
Arrays.sort(palettes, 0, palettes.length, new Comparator<BrewerPalette>(){
@Override
public int compare(BrewerPalette arg0,
BrewerPalette arg1) {
return Collator.getInstance().compare(brewerLblProvider.getText(arg0), brewerLblProvider.getText(arg1));
}});
for (BrewerPalette p : palettes){
inputs.add(p);
}
inputs.add(Messages.SingleBandEditorPage_predefinedStyleSeperator);
for (PredefinedColorRule key: colorRules.getPredefinedSets()){
inputs.add(key);
}
cmbPalette.setInput(inputs);
/* reselect based on blackboard setting */
String paletteName = (String) l.getStyleBlackboard().get("org.locationtech.udig.style.raster.palette"); //$NON-NLS-1$
if (paletteName != null){
ColorBrewer cb = getBrewer();
BrewerPalette palette = cb.getPalette(paletteName);
if (palette != null){
cmbPalette.setSelection(new StructuredSelection(cb.getPalette(paletteName)));
}else{
PredefinedColorRule cm = colorRules.getPredefinedRule(paletteName);
if (cm != null){
cmbPalette.setSelection(new StructuredSelection(cm));
}
}
}
}
@Override
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String message){
this.errorMessage = message;
super.getContainer().updateMessage();
}
}