/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.esa.snap.rcp.nodes;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.FlagCoding;
import org.esa.snap.core.datamodel.IndexCoding;
import org.esa.snap.core.datamodel.Mask;
import org.esa.snap.core.datamodel.MetadataElement;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductData;
import org.esa.snap.core.datamodel.ProductNode;
import org.esa.snap.core.datamodel.ProductNodeEvent;
import org.esa.snap.core.datamodel.ProductNodeGroup;
import org.esa.snap.core.datamodel.quicklooks.Quicklook;
import org.esa.snap.core.datamodel.RasterDataNode;
import org.esa.snap.core.datamodel.TiePointGrid;
import org.esa.snap.core.datamodel.VectorDataNode;
import org.esa.snap.core.datamodel.VirtualBand;
import org.esa.snap.core.dataop.barithm.BandArithmetic;
import org.esa.snap.core.jexp.ParseException;
import org.esa.snap.core.util.StringUtils;
import org.esa.snap.netbeans.docwin.DocumentWindow;
import org.esa.snap.netbeans.docwin.DocumentWindowManager;
import org.esa.snap.netbeans.docwin.WindowUtilities;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.actions.window.OpenImageViewAction;
import org.esa.snap.rcp.actions.window.OpenMetadataViewAction;
import org.esa.snap.rcp.actions.window.OpenPlacemarkViewAction;
import org.esa.snap.rcp.actions.window.OpenQuicklookViewAction;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.rcp.util.ProgressHandleMonitor;
import org.netbeans.api.progress.ProgressUtils;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.openide.awt.UndoRedo;
import org.openide.nodes.Node;
import org.openide.nodes.PropertySupport;
import org.openide.nodes.Sheet;
import org.openide.util.lookup.Lookups;
import javax.swing.*;
import java.awt.datatransfer.Transferable;
import java.beans.PropertyEditor;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.WeakHashMap;
import java.util.stream.Stream;
import static org.esa.snap.rcp.nodes.PNNodeSupport.performUndoableProductNodeEdit;
/**
* A node that represents some {@link ProductNode} (=PN).
*
* @author Norman
*/
abstract class PNNode<T extends ProductNode> extends PNNodeBase {
private final T productNode;
private final PNNodeSupport nodeSupport;
public PNNode(T productNode) {
this(productNode, null);
}
public PNNode(T productNode, PNGroupBase childFactory) {
super(childFactory, Lookups.singleton(productNode));
this.productNode = productNode;
setDisplayName(productNode.getName());
setShortDescription(productNode.getDescription());
nodeSupport = PNNodeSupport.create(this, childFactory);
}
public T getProductNode() {
return productNode;
}
@Override
public void nodeChanged(ProductNodeEvent event) {
if (event.getSourceNode() == getProductNode()) {
if (ProductNode.PROPERTY_NAME_NAME.equals(event.getPropertyName())) {
setDisplayName(getProductNode().getName());
}
if (ProductNode.PROPERTY_NAME_DESCRIPTION.equals(event.getPropertyName())) {
setShortDescription(getProductNode().getDescription());
}
}
nodeSupport.nodeChanged(event);
}
@Override
public void nodeDataChanged(ProductNodeEvent event) {
nodeSupport.nodeDataChanged(event);
}
@Override
public void nodeAdded(ProductNodeEvent event) {
nodeSupport.nodeAdded(event);
}
@Override
public void nodeRemoved(ProductNodeEvent event) {
nodeSupport.nodeRemoved(event);
}
@Override
public PropertySet[] getPropertySets() {
Sheet.Set set = new Sheet.Set();
set.setDisplayName("Product Node Properties");
set.put(new PropertySupport.ReadWrite<String>("name", String.class, "Name", "Name of the element") {
@Override
public String getValue() {
return getProductNode().getName();
}
@Override
public void setValue(String newValue) {
String oldValue = productNode.getName();
performUndoableProductNodeEdit("Rename",
productNode,
node -> node.setName(newValue),
node -> node.setName(oldValue)
);
}
});
set.put(new PropertySupport.ReadWrite<String>("description", String.class, "Description", "Human-readable description of the element") {
@Override
public String getValue() {
return getProductNode().getDescription();
}
@Override
public void setValue(String newValue) {
String oldValue = productNode.getDescription();
performUndoableProductNodeEdit("Edit Description",
productNode,
node -> node.setDescription(newValue),
node -> node.setDescription(oldValue)
);
}
});
set.put(new PropertySupport.ReadOnly<Boolean>("modified", Boolean.class, "Modified", "Has the element been modified?") {
@Override
public Boolean getValue() {
return getProductNode().isModified();
}
});
return new PropertySet[]{
set
};
}
@Override
public Action[] getActions(boolean context) {
ProductNode productNode1 = getProductNode();
return PNNodeSupport.getContextActions(productNode1);
}
public static Node create(ProductNode productNode) {
if (productNode instanceof FlagCoding) {
return new PNNode.FC((FlagCoding) productNode);
} else if (productNode instanceof IndexCoding) {
return new PNNode.IC((IndexCoding) productNode);
} else if (productNode instanceof MetadataElement) {
return new PNNode.ME((MetadataElement) productNode);
} else if (productNode instanceof VectorDataNode) {
return new PNNode.VDN((VectorDataNode) productNode);
} else if (productNode instanceof TiePointGrid) {
return new PNNode.TPG((TiePointGrid) productNode);
} else if (productNode instanceof Mask) {
return new PNNode.M((Mask) productNode);
} else if (productNode instanceof Band) {
return new PNNode.B((Band) productNode);
} else if (productNode instanceof Quicklook) {
return new PNNode.QL((Quicklook) productNode);
}
throw new IllegalStateException("unhandled product node type: " + productNode.getClass() + " named '" + productNode.getName() + "'");
}
private static <T extends ProductNode> void closeDocumentWindow(T productNode) {
WindowUtilities.getOpened(DocumentWindow.class)
.filter(dw -> (dw.getDocument() instanceof ProductNode) && (dw.getDocument() == productNode))
.forEach(dw -> DocumentWindowManager.getDefault().closeWindow(dw));
}
private static <T extends ProductNode> void deleteProductNode(Product product,
ProductNodeGroup<T> group,
T productNode) {
closeDocumentWindow(productNode);
final int index = group.indexOf(productNode);
group.remove(productNode);
UndoRedo.Manager manager = SnapApp.getDefault().getUndoManager(product);
if (manager != null) {
manager.addEdit(new UndoableProductNodeDeletion<>(group, productNode, index));
}
}
private static StringBuilder append(StringBuilder stringBuilder, String text) {
if (StringUtils.isNotNullAndNotEmpty(text)) {
if (stringBuilder.length() > 0) {
stringBuilder.append(", ");
}
stringBuilder.append(text);
}
return stringBuilder;
}
private static void setAncillaryVariables(Band band,
WeakHashMap<RasterDataNode, Integer> newNodes,
WeakHashMap<RasterDataNode, Integer> oldNodes) {
ArrayList<RasterDataNode> nodes = new ArrayList<>(newNodes.keySet());
Collections.sort(nodes, (n1, n2) -> newNodes.get(n1) - newNodes.get(n2));
for (RasterDataNode node : nodes) {
band.addAncillaryVariable(node);
}
oldNodes.keySet().stream().filter(oldVar -> !newNodes.containsKey(oldVar)).forEach(band::removeAncillaryVariable);
}
private static WeakHashMap<RasterDataNode, Integer> toWeakMap(RasterDataNode[] oldValue) {
WeakHashMap<RasterDataNode, Integer> oldNodes = new WeakHashMap<>();
for (int i = 0; i < oldValue.length; i++) {
RasterDataNode rasterDataNode = oldValue[i];
oldNodes.put(rasterDataNode, i);
}
return oldNodes;
}
private static void updateImages(RasterDataNode rasterDataNode, boolean validMaskPropertyChanged) {
if (rasterDataNode instanceof VirtualBand) {
VirtualBand virtualBand = (VirtualBand) rasterDataNode;
if (virtualBand.hasRasterData()) {
String title = "Recomputing Raster Data";
ProgressHandleMonitor pm = ProgressHandleMonitor.create(title);
Runnable operation = () -> {
try {
virtualBand.readRasterDataFully(pm);
} catch (IOException e) {
Dialogs.showError(e.getMessage());
}
};
ProgressUtils.runOffEventThreadWithProgressDialog(operation, title,
pm.getProgressHandle(),
true,
50, // time in ms after which wait cursor is shown
1000); // time in ms after which dialog with "Cancel" button is shown
}
}
OpenImageViewAction.updateProductSceneViewImages(new RasterDataNode[]{rasterDataNode}, view -> {
if (validMaskPropertyChanged) {
view.updateNoDataImage();
}
view.updateImage();
});
}
/**
* A node that represents a {@link MetadataElement} (=ME).
*
* @author Norman
*/
static class ME extends PNNode<MetadataElement> {
public ME(MetadataElement element) {
super(element, element.getElementGroup() != null ? new PNGGroup.ME(element.getElementGroup()) : null);
setIconBaseWithExtension("org/esa/snap/rcp/icons/RsMetaData16.gif");
}
@Override
public boolean canDestroy() {
return getProductNode().getParentElement() != null;
}
@Override
public void destroy() throws IOException {
deleteProductNode(getProductNode().getProduct(),
getProductNode().getParentElement().getElementGroup(),
getProductNode());
}
@Override
public Action getPreferredAction() {
return new OpenMetadataViewAction();
}
}
/**
* A node that represents an {@link IndexCoding} (=IC).
*
* @author Norman
*/
static class IC extends PNNode<IndexCoding> {
public IC(IndexCoding indexCoding) {
super(indexCoding);
setIconBaseWithExtension("org/esa/snap/rcp/icons/RsBandIndexes16.gif");
}
@Override
public boolean canDestroy() {
return true;
}
@Override
public void destroy() throws IOException {
deleteProductNode(getProductNode().getProduct(),
getProductNode().getProduct().getIndexCodingGroup(),
getProductNode());
}
@Override
public Action getPreferredAction() {
return new OpenMetadataViewAction();
}
}
/**
* A node that represents a {@link FlagCoding} (=FC).
*
* @author Norman
*/
static class FC extends PNNode<FlagCoding> {
public FC(FlagCoding flagCoding) {
super(flagCoding);
setIconBaseWithExtension("org/esa/snap/rcp/icons/RsBandFlags16.gif");
}
@Override
public boolean canDestroy() {
return true;
}
@Override
public void destroy() throws IOException {
deleteProductNode(getProductNode().getProduct(),
getProductNode().getProduct().getFlagCodingGroup(),
getProductNode());
}
@Override
public Action getPreferredAction() {
return new OpenMetadataViewAction();
}
}
/**
* A node that represents a {@link VectorDataNode} (=VDN).
*
* @author Norman
*/
static class VDN extends PNNode<VectorDataNode> {
public VDN(VectorDataNode vectorDataNode) {
super(vectorDataNode);
setIconBaseWithExtension("org/esa/snap/rcp/icons/RsVectorData16.gif");
setShortDescription(createToolTip(vectorDataNode));
}
@Override
public boolean canDestroy() {
return true;
}
@Override
public void destroy() throws IOException {
deleteProductNode(getProductNode().getProduct(),
getProductNode().getProduct().getVectorDataGroup(),
getProductNode());
}
@Override
public Action getPreferredAction() {
return new OpenPlacemarkViewAction();
}
@Override
public PropertySet[] getPropertySets() {
Sheet.Set set = new Sheet.Set();
final VectorDataNode vdn = getProductNode();
set.setDisplayName("Vector Data Properties");
set.put(new PropertySupport.ReadOnly<String>("featureType", String.class, "Feature type",
"The feature type schema used for all features in the collection") {
@Override
public String getValue() {
return vdn.getFeatureType().getTypeName();
}
});
set.put(new PropertySupport.ReadOnly<String>("featureGeomType", String.class, "Feature geometry type",
"The geometry type used used for all feature geometries in the collection") {
@Override
public String getValue() {
Class<?> binding;
GeometryDescriptor geometryDescriptor = vdn.getFeatureType().getGeometryDescriptor();
if (geometryDescriptor != null) {
binding = geometryDescriptor.getType().getBinding();
} else {
binding = null;
}
return binding != null ? binding.getName() : "<unknown>";
}
});
set.put(new PropertySupport.ReadOnly<String>("featureCRS", String.class, "Feature geometry CRS",
"The coordinate reference system used used for all feature geometries in the collection") {
@Override
public String getValue() {
CoordinateReferenceSystem crs;
GeometryDescriptor geometryDescriptor = vdn.getFeatureType().getGeometryDescriptor();
if (geometryDescriptor != null) {
crs = geometryDescriptor.getType().getCoordinateReferenceSystem();
} else {
crs = null;
}
return crs != null ? crs.toString() : "<unknown>";
}
});
set.put(new PropertySupport.ReadOnly<Integer>("featureCount", Integer.class, "Feature count",
"The number of features in this collection") {
@Override
public Integer getValue() {
return vdn.getFeatureCollection().size();
}
});
return Stream.concat(Stream.of(super.getPropertySets()), Stream.of(set)).toArray(PropertySet[]::new);
}
private String createToolTip(final VectorDataNode vectorDataNode) {
final StringBuilder tooltip = new StringBuilder();
append(tooltip, vectorDataNode.getDescription());
append(tooltip, String.format("type %s, %d feature(s)", vectorDataNode.getFeatureType().getTypeName(),
vectorDataNode.getFeatureCollection().size()));
return tooltip.toString();
}
}
/**
* A node that represents a {@link TiePointGrid} (=TPG).
*
* @author Norman
*/
static class TPG extends PNNode<TiePointGrid> {
public TPG(TiePointGrid tiePointGrid) {
super(tiePointGrid);
setIconBaseWithExtension("org/esa/snap/rcp/icons/RsBandAsTiePoint16.gif");
setShortDescription(createToolTip(tiePointGrid));
}
@Override
public boolean canDestroy() {
return true;
}
@Override
public void destroy() throws IOException {
deleteProductNode(getProductNode().getProduct(),
getProductNode().getProduct().getTiePointGridGroup(),
getProductNode());
}
@Override
public Action getPreferredAction() {
return OpenImageViewAction.create(this.getProductNode());
}
@Override
public PropertySet[] getPropertySets() {
Sheet.Set set = new Sheet.Set();
final TiePointGrid tpg = getProductNode();
set.setDisplayName("Tie-Point Grid Properties");
set.put(new UnitProperty(tpg));
set.put(new RasterSizeProperty(tpg));
set.put(new ValidPixelExpressionProperty(tpg));
set.put(new NoDataValueUsedProperty(tpg));
set.put(new NoDataValueProperty(tpg));
return Stream.concat(Stream.of(super.getPropertySets()), Stream.of(set)).toArray(PropertySet[]::new);
}
private String createToolTip(final TiePointGrid tiePointGrid) {
StringBuilder tooltip = new StringBuilder();
append(tooltip, tiePointGrid.getDescription());
append(tooltip, String.format("%d x %d --> %d x %d pixels",
tiePointGrid.getGridWidth(), tiePointGrid.getGridHeight(),
tiePointGrid.getRasterWidth(), tiePointGrid.getRasterHeight()));
if (tiePointGrid.getUnit() != null) {
append(tooltip, String.format(" (%s)", tiePointGrid.getUnit()));
}
return tooltip.toString();
}
}
/**
* A node that represents a {@link Mask} (=M).
*
* @author Norman
*/
static class M extends PNNode<Mask> {
public M(Mask mask) {
super(mask);
setIconBaseWithExtension("org/esa/snap/rcp/icons/RsMask16.gif");
}
@Override
public boolean canDestroy() {
return true;
}
@Override
public void destroy() throws IOException {
deleteProductNode(getProductNode().getProduct(),
getProductNode().getProduct().getMaskGroup(),
getProductNode());
}
@Override
public Action getPreferredAction() {
return OpenImageViewAction.create(this.getProductNode());
}
}
/**
* A node that represents a {@link Band} (=B).
*
* @author Norman
*/
static class B extends PNNode<Band> {
public B(Band band) {
super(band);
if (band instanceof VirtualBand) {
setIconBaseWithExtension("org/esa/snap/rcp/icons/RsBandVirtual16.gif");
} else if (band.isFlagBand()) {
setIconBaseWithExtension("org/esa/snap/rcp/icons/RsBandFlags16.gif");
} else {
setIconBaseWithExtension("org/esa/snap/rcp/icons/RsBandAsSwath.gif");
}
if (band.getSpectralWavelength() > 0.0) {
if (band.getSpectralWavelength() == Math.round(band.getSpectralWavelength())) {
setDisplayName(String.format("%s (%d nm)", band.getName(), (int) band.getSpectralWavelength()));
} else {
setDisplayName(String.format("%s (%s nm)", band.getName(), String.valueOf(band.getSpectralWavelength())));
}
}
setShortDescription(createToolTip(band));
}
@Override
public boolean canDestroy() {
return true;
}
@Override
public void destroy() throws IOException {
deleteProductNode(getProductNode().getProduct(),
getProductNode().getProduct().getBandGroup(),
getProductNode());
}
@Override
public Transferable clipboardCopy() throws IOException {
return super.clipboardCopy();
}
@Override
public boolean canCopy() {
return true;
}
@Override
public Transferable clipboardCut() throws IOException {
return super.clipboardCut();
}
@Override
public boolean canCut() {
return true;
}
@Override
public Action getPreferredAction() {
return OpenImageViewAction.create(this.getProductNode());
}
@Override
public PropertySet[] getPropertySets() {
Sheet.Set set = new Sheet.Set();
final Band band = getProductNode();
set.setDisplayName("Raster Band Properties");
set.put(new UnitProperty(band));
set.put(new DataTypeProperty(band));
set.put(new RasterSizeProperty(band));
if (band instanceof VirtualBand) {
final VirtualBand virtualBand = (VirtualBand) band;
set.put(new PropertySupport.ReadWrite<String>("expression", String.class, "Pixel-Value Expression",
"Mathematical expression used to compute the raster's pixel values") {
@Override
public String getValue() {
return virtualBand.getExpression();
}
@Override
public void setValue(String newValue) {
String oldValue = virtualBand.getExpression();
performUndoableProductNodeEdit("Edit Pixel-Value Expression",
virtualBand,
node -> {
node.setExpression(newValue);
updateImages(node, false);
},
node -> {
node.setExpression(oldValue);
updateImages(node, false);
}
);
}
});
}
set.put(new ValidPixelExpressionProperty(band));
set.put(new NoDataValueUsedProperty(band));
set.put(new NoDataValueProperty(band));
set.put(new PropertySupport.ReadWrite<Float>("spectralWavelength", Float.class, "Spectral Wavelength",
"The spectral wavelength in nanometers") {
@Override
public Float getValue() {
return band.getSpectralWavelength();
}
@Override
public void setValue(Float newValue) {
float oldValue = band.getSpectralWavelength();
performUndoableProductNodeEdit("Edit Spectral Wavelength",
band,
node -> node.setSpectralWavelength(newValue),
node -> node.setSpectralWavelength(oldValue));
}
}
);
set.put(new PropertySupport.ReadWrite<Float>("spectralBandWidth", Float.class, "Spectral Bandwidth",
"The spectral bandwidth in nanometers") {
@Override
public Float getValue() {
return band.getSpectralBandwidth();
}
@Override
public void setValue(Float newValue) {
float oldValue = band.getSpectralBandwidth();
performUndoableProductNodeEdit("Edit Spectral Bandwidth",
band,
node -> node.setSpectralBandwidth(newValue),
node -> node.setSpectralBandwidth(oldValue));
}
}
);
Property<RasterDataNode[]> ancillaryVariables = new PropertySupport.ReadWrite<RasterDataNode[]>("ancillaryVariables",
RasterDataNode[].class,
"Ancillary Variables",
"Other rasters that are ancillary variables for this raster (NetCDF-U 'ancillary_variables' attribute)") {
private WeakReference<RastersPropertyEditor> propertyEditorRef;
@Override
public RasterDataNode[] getValue() {
return band.getAncillaryVariables();
}
@Override
public void setValue(RasterDataNode[] newValue) {
WeakHashMap<RasterDataNode, Integer> oldNodes = toWeakMap(band.getAncillaryVariables());
WeakHashMap<RasterDataNode, Integer> newNodes = toWeakMap(newValue);
performUndoableProductNodeEdit("Edit Ancillary Variables",
band,
node -> setAncillaryVariables(node, newNodes, oldNodes),
node -> setAncillaryVariables(node, oldNodes, newNodes));
}
@Override
public PropertyEditor getPropertyEditor() {
RastersPropertyEditor propertyEditor = null;
if (propertyEditorRef != null) {
propertyEditor = propertyEditorRef.get();
}
if (propertyEditor == null) {
propertyEditor = new RastersPropertyEditor(band.getProduct().getBands());
propertyEditorRef = new WeakReference<>(propertyEditor);
}
return propertyEditor;
}
};
set.put(ancillaryVariables);
set.put(new PropertySupport.ReadWrite<String[]>("ancillaryRelations",
String[].class,
"Ancillary Relations",
"Relation names if this raster is an ancillary variable (NetCDF-U 'rel' attribute)") {
@Override
public String[] getValue() {
return band.getAncillaryRelations();
}
@Override
public void setValue(String[] newValue) {
String[] oldValue = band.getAncillaryRelations();
performUndoableProductNodeEdit("Edit Ancillary Relations",
band,
node -> node.setAncillaryRelations(newValue),
node -> node.setAncillaryRelations(oldValue));
}
}
);
return Stream.concat(Stream.of(super.getPropertySets()), Stream.of(set)).toArray(PropertySet[]::new);
}
private String createToolTip(final Band band) {
StringBuilder tooltip = new StringBuilder();
append(tooltip, band.getDescription());
if (band.getSpectralWavelength() > 0.0) {
append(tooltip, String.format("%s nm", band.getSpectralWavelength()));
if (band.getSpectralBandwidth() > 0.0) {
append(tooltip, String.format("+/-%s nm", 0.5 * band.getSpectralBandwidth()));
}
}
append(tooltip, String.format("%d x %d pixels", band.getRasterWidth(), band.getRasterHeight()));
if (band instanceof VirtualBand) {
append(tooltip, String.format("Expr.: %s", ((VirtualBand) band).getExpression()));
}
if (band.getUnit() != null) {
append(tooltip, String.format(" (%s)", band.getUnit()));
}
return tooltip.toString();
}
}
/**
* A node that represents a {@link Quicklook} (=QL).
*
* @author Luis
*/
static class QL extends PNNode<Quicklook> {
public QL(Quicklook ql) {
super(ql);
setIconBaseWithExtension("org/esa/snap/rcp/icons/quicklook16.png");
}
@Override
public boolean canDestroy() {
return true;
}
@Override
public void destroy() throws IOException {
deleteProductNode(getProductNode().getProduct(),
getProductNode().getProduct().getQuicklookGroup(),
getProductNode());
}
@Override
public Action getPreferredAction() {
return new OpenQuicklookViewAction();
}
}
private static class DataTypeProperty extends PropertySupport.ReadOnly<String> {
private final RasterDataNode raster;
public DataTypeProperty(RasterDataNode raster) {
super("dataType", String.class, "Data Type", "Raster data type");
this.raster = raster;
}
@Override
public String getValue() {
return ProductData.getTypeString(raster.getDataType());
}
}
private static class RasterSizeProperty extends PropertySupport.ReadOnly<String> {
private final RasterDataNode raster;
public RasterSizeProperty(RasterDataNode raster) {
super("rasterSize", String.class, "Raster size", "Width and height of the raster in pixels");
this.raster = raster;
}
@Override
public String getValue() {
return String.format("%d x %d", raster.getRasterWidth(), raster.getRasterHeight());
}
}
private static class UnitProperty extends PropertySupport.ReadWrite<String> {
private final RasterDataNode raster;
public UnitProperty(RasterDataNode raster) {
super("unit", String.class, "Unit", "Geophysical Unit");
this.raster = raster;
}
@Override
public String getValue() {
return raster.getUnit();
}
@Override
public void setValue(String newValue) {
String oldValue = raster.getUnit();
performUndoableProductNodeEdit("Edit Unit",
raster,
node -> node.setUnit(newValue),
node -> node.setUnit(oldValue)
);
}
}
private static class ValidPixelExpressionProperty extends PropertySupport.ReadWrite<String> {
private final RasterDataNode raster;
public ValidPixelExpressionProperty(RasterDataNode raster) {
super("validPixelExpression", String.class, "Valid-Pixel Expression", "Boolean expression which is used to identify valid pixels");
this.raster = raster;
}
@Override
public String getValue() {
final String expression = raster.getValidPixelExpression();
if (expression != null) {
return expression;
}
return "";
}
@Override
public void setValue(String newValue) {
try {
final Product product = raster.getProduct();
final RasterDataNode[] refRasters = BandArithmetic.getRefRasters(newValue, product);
if (refRasters.length > 0 &&
(!BandArithmetic.areRastersEqualInSize(product, newValue) ||
refRasters[0].getRasterHeight() != raster.getRasterHeight() ||
refRasters[0].getRasterWidth() != raster.getRasterWidth())) {
Dialogs.showInformation("Referenced rasters must all be the same size", null);
} else {
String oldValue = raster.getValidPixelExpression();
performUndoableProductNodeEdit("Edit Valid-Pixel Expression",
raster,
node -> {
node.setValidPixelExpression(newValue);
updateImages(node, true);
},
node -> {
node.setValidPixelExpression(oldValue);
updateImages(node, true);
}
);
}
} catch (ParseException e) {
Dialogs.showError("Expression is invalid: " + e.getMessage());
}
}
}
private static class NoDataValueProperty extends PropertySupport.ReadWrite<Double> {
private final RasterDataNode raster;
public NoDataValueProperty(RasterDataNode raster) {
super("noDataValue", Double.class, "No-Data Value", "No-data value used to indicate missing pixels");
this.raster = raster;
}
@Override
public Double getValue() {
return raster.getNoDataValue();
}
@Override
public void setValue(Double newValue) {
double oldValue = raster.getNoDataValue();
performUndoableProductNodeEdit("Edit No-Data Value",
raster,
node -> {
node.setNoDataValue(newValue);
updateImages(node, true);
},
node -> {
node.setNoDataValue(oldValue);
updateImages(node, true);
}
);
}
}
private static class NoDataValueUsedProperty extends PropertySupport.ReadWrite<Boolean> {
private final RasterDataNode raster;
public NoDataValueUsedProperty(RasterDataNode raster) {
super("noDataValueUsed", Boolean.class, "No-Data Value Used", "Is the no-data value in use?");
this.raster = raster;
}
@Override
public Boolean getValue() {
return raster.isNoDataValueUsed();
}
@Override
public void setValue(Boolean newValue) {
Boolean oldValue = raster.isNoDataValueUsed();
performUndoableProductNodeEdit("Edit No-Data Value Used",
raster,
node -> {
node.setNoDataValueUsed(newValue);
updateImages(node, true);
},
node -> {
node.setNoDataValueUsed(oldValue);
updateImages(node, true);
}
);
}
}
}