/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package org.esa.snap.rcp.layermanager.layersrc.product;
import com.bc.ceres.binding.PropertySet;
import com.bc.ceres.glayer.Layer;
import com.bc.ceres.glayer.LayerType;
import com.bc.ceres.glayer.LayerTypeRegistry;
import com.bc.ceres.glayer.support.ImageLayer;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductManager;
import org.esa.snap.core.datamodel.RasterDataNode;
import org.esa.snap.core.datamodel.TiePointGrid;
import org.esa.snap.core.layer.RasterImageLayerType;
import org.esa.snap.core.util.ObjectUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.ui.layer.AbstractLayerSourceAssistantPage;
import org.esa.snap.ui.product.ProductSceneView;
import org.geotools.referencing.CRS;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
class ProductLayerAssistantPage extends AbstractLayerSourceAssistantPage {
private JTree tree;
ProductLayerAssistantPage() {
super("Select Band / Tie-Point Grid");
}
@Override
public Component createPageComponent() {
ProductTreeModel model = createTreeModel();
tree = new JTree(model);
tree.setEditable(false);
tree.setShowsRootHandles(true);
tree.setRootVisible(false);
tree.setCellRenderer(new ProductNodeTreeCellRenderer());
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
tree.getSelectionModel().addTreeSelectionListener(new ProductNodeSelectionListener());
List<CompatibleNodeList> nodeLists = model.compatibleNodeLists;
for (CompatibleNodeList nodeList : nodeLists) {
tree.expandPath(new TreePath(new Object[]{nodeLists, nodeList}));
}
JPanel panel = new JPanel(new BorderLayout(4, 4));
panel.setBorder(new EmptyBorder(4, 4, 4, 4));
panel.add(new JLabel("Compatible bands and tie-point grids:"), BorderLayout.NORTH);
panel.add(new JScrollPane(tree), BorderLayout.CENTER);
return panel;
}
@Override
public boolean validatePage() {
TreePath path = tree.getSelectionPath();
return path != null && path.getLastPathComponent() instanceof RasterDataNode;
}
@Override
public boolean hasNextPage() {
return false;
}
@Override
public boolean canFinish() {
return true;
}
@Override
public boolean performFinish() {
//allow multiple selections
final TreePath[] selectionPaths = tree.getSelectionPaths();
if (selectionPaths == null) {
return false;
}
for (TreePath treePath : selectionPaths) {
final RasterDataNode rasterDataNode = (RasterDataNode) treePath.getLastPathComponent();
LayerType type = LayerTypeRegistry.getLayerType(RasterImageLayerType.class.getName());
PropertySet configuration = type.createLayerConfig(getContext().getLayerContext());
configuration.setValue(RasterImageLayerType.PROPERTY_NAME_RASTER, rasterDataNode);
configuration.setValue(ImageLayer.PROPERTY_NAME_BORDER_SHOWN, false);
configuration.setValue(ImageLayer.PROPERTY_NAME_BORDER_COLOR, ImageLayer.DEFAULT_BORDER_COLOR);
configuration.setValue(ImageLayer.PROPERTY_NAME_BORDER_WIDTH, ImageLayer.DEFAULT_BORDER_WIDTH);
configuration.setValue(ImageLayer.PROPERTY_NAME_PIXEL_BORDER_SHOWN, false);
final ImageLayer imageLayer = (ImageLayer) type.createLayer(getContext().getLayerContext(),
configuration);
imageLayer.setName(rasterDataNode.getDisplayName());
ProductSceneView sceneView = SnapApp.getDefault().getSelectedProductSceneView();
Layer rootLayer = sceneView.getRootLayer();
rootLayer.getChildren().add(sceneView.getFirstImageLayerIndex(), imageLayer);
final LayerDataHandler layerDataHandler = new LayerDataHandler(rasterDataNode, imageLayer);
rasterDataNode.getProduct().addProductNodeListener(layerDataHandler);
rootLayer.addListener(layerDataHandler);
}
return true;
}
private static class CompatibleNodeList {
private final String name;
private final List<RasterDataNode> rasterDataNodes;
CompatibleNodeList(String name, List<RasterDataNode> rasterDataNodes) {
this.name = name;
this.rasterDataNodes = rasterDataNodes;
}
}
private ProductTreeModel createTreeModel() {
final ProductSceneView selectedProductSceneView = SnapApp.getDefault().getSelectedProductSceneView();
Product selectedProduct = selectedProductSceneView.getProduct();
RasterDataNode raster = selectedProductSceneView.getRaster();
CoordinateReferenceSystem modelCRS = selectedProduct.getSceneCRS();
ArrayList<CompatibleNodeList> compatibleNodeLists = new ArrayList<>(3);
List<RasterDataNode> compatibleNodes = new ArrayList<>();
collectCompatibleBands(raster, selectedProduct.getBands(), compatibleNodes);
if (raster.getRasterWidth() == selectedProduct.getSceneRasterWidth() &&
raster.getRasterHeight() == selectedProduct.getSceneRasterHeight()) {
compatibleNodes.addAll(Arrays.asList(selectedProduct.getTiePointGrids()));
}
if (!compatibleNodes.isEmpty()) {
compatibleNodeLists.add(new CompatibleNodeList(selectedProduct.getDisplayName(), compatibleNodes));
}
if (modelCRS != null) {
final ProductManager productManager = SnapApp.getDefault().getProductManager();
final Product[] products = productManager.getProducts();
for (Product product : products) {
if (product == selectedProduct) {
continue;
}
compatibleNodes = new ArrayList<>();
collectCompatibleRasterDataNodes(modelCRS, product.getBands(), compatibleNodes);
collectCompatibleRasterDataNodes(modelCRS, product.getTiePointGrids(), compatibleNodes);
if (!compatibleNodes.isEmpty()) {
compatibleNodeLists.add(new CompatibleNodeList(product.getDisplayName(), compatibleNodes));
}
}
}
return new ProductTreeModel(compatibleNodeLists);
}
private void collectCompatibleBands(RasterDataNode referenceRaster, RasterDataNode[] dataNodes,
Collection<RasterDataNode> rasterDataNodes) {
final Dimension referenceRasterSize = referenceRaster.getRasterSize();
for (RasterDataNode node : dataNodes) {
if (node.getRasterSize().equals(referenceRasterSize)) {
rasterDataNodes.add(node);
}
}
}
private void collectCompatibleRasterDataNodes(CoordinateReferenceSystem thisCrs,
RasterDataNode[] bands, Collection<RasterDataNode> rasterDataNodes) {
for (RasterDataNode node : bands) {
CoordinateReferenceSystem otherCrs = Product.findModelCRS(node.getGeoCoding());
// For GeoTools, two CRS where unequal if the authorities of their CS only differ in version
// This happened with the S-2 L1C CRS, namely an EPSG:32615. Here one authority's version was null,
// the other "7.9". Extremely annoying to debug and find out :-( (nf, Feb 2013)
if (CRS.equalsIgnoreMetadata(thisCrs, otherCrs)
|| haveCommonReferenceIdentifiers(thisCrs, otherCrs)) {
rasterDataNodes.add(node);
}
}
}
private static boolean haveCommonReferenceIdentifiers(CoordinateReferenceSystem crs1, CoordinateReferenceSystem crs2) {
Set<ReferenceIdentifier> identifiers1 = crs1.getIdentifiers();
Set<ReferenceIdentifier> identifiers2 = crs2.getIdentifiers();
// If a CRS does not have identifiers or if they have different number of identifiers
// they cannot be equal.
if (identifiers1 == null || identifiers1.isEmpty()
|| identifiers2 == null || identifiers2.isEmpty()
|| identifiers1.size() != identifiers2.size()) {
return false;
}
// The two CRSs can only be equal if they have the same number of identifiers
// and all of them are common to both.
int eqCount = 0;
for (ReferenceIdentifier refId1 : identifiers1) {
for (ReferenceIdentifier refId2 : identifiers2) {
if (compareRefIds(refId1, refId2)) {
eqCount++;
break;
}
}
}
return eqCount == identifiers1.size();
}
private static boolean compareRefIds(ReferenceIdentifier refId1, ReferenceIdentifier refId2) {
return ObjectUtils.equalObjects(refId1.getCodeSpace(), refId2.getCodeSpace())
&& ObjectUtils.equalObjects(refId1.getCode(), refId2.getCode())
&& compareVersions(refId1.getVersion(), refId2.getVersion());
}
// Other than GeoTools, we compare versions only if given.
// We interpret the case version==null, as not provided, hence all versions match (nf, Feb 2013)
private static boolean compareVersions(String version1, String version2) {
return version1 == null || version2 == null || version1.equalsIgnoreCase(version2);
}
private static class ProductNodeTreeCellRenderer extends DefaultTreeCellRenderer {
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
JLabel label = (JLabel) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
if (value instanceof CompatibleNodeList) {
label.setText(MessageFormat.format("<html><b>{0}</b></html>", ((CompatibleNodeList) value).name));
} else if (value instanceof Band) {
label.setText(MessageFormat.format("<html><b>{0}</b></html>", ((Band) value).getName()));
} else if (value instanceof TiePointGrid) {
label.setText(MessageFormat.format("<html><b>{0}</b> (Tie-point grid)</html>",
((TiePointGrid) value).getName()));
}
return label;
}
}
private class ProductNodeSelectionListener implements TreeSelectionListener {
@Override
public void valueChanged(TreeSelectionEvent e) {
getContext().updateState();
}
}
private static class ProductTreeModel implements TreeModel {
private final WeakHashMap<TreeModelListener, Object> treeModelListeners;
private final List<CompatibleNodeList> compatibleNodeLists;
private ProductTreeModel(List<CompatibleNodeList> compatibleNodeLists) {
this.compatibleNodeLists = compatibleNodeLists;
this.treeModelListeners = new WeakHashMap<>();
}
@Override
public Object getRoot() {
return compatibleNodeLists;
}
@Override
public Object getChild(Object parent, int index) {
if (parent == compatibleNodeLists) {
return compatibleNodeLists.get(index);
} else if (parent instanceof CompatibleNodeList) {
return ((CompatibleNodeList) parent).rasterDataNodes.get(index);
}
return null;
}
@Override
public int getChildCount(Object parent) {
if (parent == compatibleNodeLists) {
return compatibleNodeLists.size();
} else if (parent instanceof CompatibleNodeList) {
return ((CompatibleNodeList) parent).rasterDataNodes.size();
}
return 0;
}
@Override
public boolean isLeaf(Object node) {
return node instanceof RasterDataNode;
}
@Override
public int getIndexOfChild(Object parent, Object child) {
if (parent == compatibleNodeLists) {
return compatibleNodeLists.indexOf(child);
} else if (parent instanceof CompatibleNodeList) {
return ((CompatibleNodeList) parent).rasterDataNodes.indexOf(child);
}
return -1;
}
@Override
public void valueForPathChanged(TreePath path, Object newValue) {
fireTreeNodeChanged(path);
}
@Override
public void addTreeModelListener(TreeModelListener l) {
treeModelListeners.put(l, "");
}
@Override
public void removeTreeModelListener(TreeModelListener l) {
treeModelListeners.remove(l);
}
protected void fireTreeNodeChanged(TreePath treePath) {
TreeModelEvent event = new TreeModelEvent(this, treePath);
for (TreeModelListener treeModelListener : treeModelListeners.keySet()) {
treeModelListener.treeNodesChanged(event);
}
}
}
}